HTML 5 Mashup Player new-model
authorveltr
Wed, 20 Jun 2012 14:45:30 +0200
branchnew-model
changeset 917 eb8677d3a663
parent 916 ec6849bbbdcc
child 918 5cd368dba1b9
HTML 5 Mashup Player
doc/data-api.md
src/js/defaults.js
src/js/init.js
src/js/libs/json2.js
src/js/model.js
src/js/players/player.mashup-html.js
src/js/pop.js
src/js/serializers/ldt.js
src/widgets/Controller.js
src/widgets/MediaList.css
src/widgets/MediaList.js
test/mashup/bab_files/player_bab_ldt.swf
test/mashup/moon.htm
test/mashup/moon/apollo.jpg
test/mashup/moon/atlas.jpg
test/mashup/moon/melies.jpg
test/mashup/moon/moon.json
test/mp4video.htm
test/oggvideo.htm
test/test-config.js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/doc/data-api.md	Wed Jun 20 14:45:30 2012 +0200
@@ -0,0 +1,304 @@
+# API d’accès aux métadonnées #
+
+ATTENTION !
+Cette documentation se réfère à la v.3 du Metadataplayer, actuellement disponible dans la branche **new-model** du repository
+http://www.iri.centrepompidou.fr/dev/hg/metadataplayer
+
+## Élément de base ##
+
+    IriSP.Model.Element
+
+### Rôle ###
+
+Classe de base dont héritent les différents types d’objets utilisés dans le Metadataplayer: annotations, types d’annotations, médias, etc.
+
+### Instanciation ###
+
+**Element** fonctionne comme une classe abstraite est n’est jamais instancié directement.
+
+Néanmoins, tous les objets en héritant seront instanciés de la manière suivante :
+
+    var myElement = new IriSP.Model.Element(id, source);
+
+- **id** est l’identifiant unique de l’élément. S’il est à *false*, un identifiant unique sera généré.
+- **source** identifie la source de données dont provient l’élément (cf. Source de Données, *IriSP.Model.Source*, plus bas).
+
+### Propriétés ###
+
+#### type ####
+
+Type d’élément, surchargé par les classes qui héritent de l’élément de base:
+
+- **element** pour IriSP.Model.Element
+- **media** pour IriSP.Model.Media
+- **annotationType** pour IriSP.Model.AnnotationType
+- **tag** pour IriSP.Model.Tag
+- **annotation** pour IriSP.Model.Annotation
+- **mashup** pour IriSP.Model.Mashup
+- **mashedAnnotation** pour IriSP.Model.MashedAnnotation
+
+#### id ####
+
+Identifiant unique de l’élément
+
+#### title ####
+
+Titre de l’élément, par défaut une chaîne vide ("")
+
+#### description ####
+
+Description de l’élément, par défaut une chaîne vide ("")
+
+## Media ##
+
+    IriSP.Model.Media
+
+### Rôle ###
+
+Représente un média (vidéo ou audio).
+
+Hérite de l’Élément de base
+
+### Propriétés ###
+
+#### video ####
+
+Il s’agit de l’URL de la vidéo à charger
+
+#### duration ####
+
+Il s’agit de la durée du média (telle que renseignée dans les métadonnées -- peut ne pas être égale à la durée telle que lue dans la fenêtre vidéo).
+
+Il s’agit d’un objet durée (cf. *IriSP.Model.Time* plus bas)
+
+### Méthodes ###
+
+#### getDuration ####
+
+Permet de spécifier la durée du média, en millisecondes
+
+#### getAnnotations ####
+
+Retourne la liste des annotations associées au média
+
+#### getAnnotationsByTypeTitle ####
+
+Retourne la liste des annotations associées au média et dont le type d’annotation (ou découpage, ou ligne, c.f. Type d’Annotation plus bas) correspond à l’argument de la fonction
+
+## Type d’Annotation ##
+
+    IriSP.Model.AnnotationType
+
+### Rôle ###
+
+Représente un type d’annotation, correspondant également à ce qui peut être nommé découpage ou ligne dans *Lignes de Temps*
+
+Hérite de l’Élément de base.
+
+### Méthodes ###
+
+#### getAnnotations ####
+
+Retourne la liste des annotations associées au type d’annotation
+
+## Annotation ##
+
+    IriSP.Model.Annotation
+
+### Rôle ###
+
+Représente une annotation, correspondant à un segment temporel (dont la durée peut être nulle) d’un média
+
+Hérite de l’Élément de base.
+
+### Propriétés ###
+
+#### begin ####
+
+Timecode de fin de l’annotation. Est un objet de type durée (cf. plus bas)
+
+#### begin ####
+
+Timecode de début de l’annotation. Est un objet de type durée (cf. plus bas)
+
+### Méthodes ###
+
+#### getMedia ####
+
+Retourne l’objet **Média** (*IriSP.Model.Media*) auquel se réfère l’annotation
+
+#### getAnnotationType ####
+
+Retourne l’objet **Type d’Annotation** (*IriSP.Model.AnnotationType*) auquel se réfère l’annotation
+
+#### getTags ####
+
+Retourne la liste (cf. Liste d’éléments *IriSP.Model.List*) des tags associés à l’annotation.
+
+#### getTagTexts ####
+
+## Mashup ##
+
+    IriSP.Model.Mashup
+
+### Rôle ###
+
+Il s’agit d’un bout à bout, composé d’une liste de segments (définis par des annotations de durée non nulle) accolés les uns après les autres.
+
+### Méthodes ###
+
+**À compléter**
+
+## Liste d’éléments ##
+
+    IriSP.Model.List
+
+### Rôle ###
+
+Etend les fonctionnalités des tableaux javascript (*Array*) pour lister des éléments (cf. types d’éléments ci-dessus).
+
+### Instanciation ###
+
+    var myList = new IriSP.Model.List(directory);
+
+- **directory** est le répertoire de données auxquelles la liste permet d’accéder (cf. plus bas)
+
+### Méthodes ###
+
+#### Méthodes de parcours de liste ####
+
+Ces méthodes sont fournies grâce à la bibliothèque extérieure *underscore.js* et sont documentées sur http://documentcloud.github.com/underscore/
+
+Il s’agit de:
+
+- **map**: Renvoie un tableau (*Array*) dont les éléments correspondent aux éléments de la liste, via une fonction passée en argument de map
+- **forEach**: Itère une fonction sur la liste.
+- **filter**: Ne renvoie que les éléments de la liste dont la valeur correspond au résultat d’une fonction.
+- **sortBy**: Fonction de tri, par ordre croissant de la valeur retournée par la fonction passée en argument.
+
+#### searchByTitle, searchByDescription, searchByTextFields ####
+
+Méthodes retournant une nouvelle liste d’éléments, contenant les éléments de la liste dont respectivement le titre, la description ou les deux correspondent à l’argument de la méthode.
+
+    myList.searchByTitle("texte"); // => un *IriSP.Model.List* contenant les éléments de myList dont le titre contient "texte"
+
+## Durée ##
+
+    IriSP.Model.Time
+
+### Rôle ###
+
+Facilite la gestion des durées en millisecondes utilisées dans le Metadataplayer
+
+### Instanciation ###
+
+    var myTime = new IriSP.Model.Time(ms);
+
+- **ms** est une durée en millisecondes
+
+### Méthodes ###
+
+#### getSeconds ####
+
+Renvoie la durée convertie en secondes
+
+#### toString ####
+
+Renvoie la durée au format (hh:)mm:ss
+
+#### setSeconds ####
+
+Permet d’affecter une durée en secondes
+
+    myTime.setSeconds(12); // 12000 millisecondes
+
+## Source de données ##
+
+    IriSP.Model.Source
+
+et
+    IriSP.Model.RemoteSource
+
+### Rôle ###
+
+Gère une source de données : fichier externe JSON, XML, etc. pour *IriSP.Model.RemoteSource*, projet créé à la volée pour *IriSP.Model.Source*.
+
+*IriSP.Model.RemoteSource* hérite de *IriSP.Model.Source* et ne diffère que par son implémentation de la méthode *get*.
+
+Sur la plateforme *Lignes de Temps*, il existe plusieurs API qui sont utilisées comme sources :
+
+- L’API projet, qui renvoie un fichier JSON contenant un projet LDT complet.
+- L’API segment, qui renvoie toutes les annotations d’un média situées entre deux timecodes fournis en argument.
+- L’API de publication d’annotation, qui demande l’envoi (par la méthode HTTP PUT) d’une liste d’annotation et renvoie celle-ci en retour, avec les identifiants des annotations en base de données.
+
+### Instanciation ###
+
+    var config = { directory: myDirectory };
+    var mySource = new IriSP.Model.Source(config);
+
+- **config** est un objet contenant les options de configuration:
+    - Il doit nécessairement contenir une propriété **directory**, désignant le répertoire de données (cf. plus bas).
+    - La propriété **serializer** doit désigner le *Sérialiseur* utilisé pour désérialiser les données importées ou sérialiser l’export.
+    - un *IriSP.Model.RemoteSource* doit également être appelé avec une propriété **url**, désignant l’URL de la source.
+
+Une Source ne doit pas être instanciée directement, ce rôle est donné aux répertoires de données, ce qui permet notamment d’éviter des accès multiples à une même URL.
+
+### Propriétés ###
+
+#### currentMedia ####
+
+*TODO: transférer dans un objet "Project"*
+
+Donne accès au média en cours du projet. Peut désigner un vrai média ou un mashup.
+
+### Méthodes ###
+
+#### get ####
+
+Permet de récupérer ou de rafraîchir, via Ajax, les données de la source. Pour un *IriSP.Model.Source* de base, n’a aucun effet.
+
+#### onLoad ####
+
+Permet d’exécuter une fonction, passée en argument, au chargement de la source.
+
+#### serialize, deSerialize ####
+
+Transforme les données de la source en données sérialisées, au format du sérialiseur associées à la source, et inversement.
+
+#### getAnnotations, getAnnotationTypes, getMedias, getTags, getMashups ####
+
+Retourne les listes respectives d’annotations, types d’annotations, médias, tags et mashups de la source.
+
+#### getAnnotationsByTypeTitle ####
+
+Retourne la liste des annotations dont le type d’annotation correspond à l’argument de la fonction.
+
+## Répertoire de données ##
+
+    IriSP.Model.Directory
+
+### Rôle ###
+
+Gère l’instanciation des sources de données et la mise en cache de ces sources lorsque plusieurs appels à la même URLs sont faits.
+
+Permet également aux objets de plusieurs sources d’interagir entre eux.
+
+### Instanciation ###
+
+    var myDirectory = new IriSP.Model.Directory
+
+### Méthodes ###
+
+#### newLocalSource ####
+
+Crée une nouvelle source non attachée à une URL. S’il faut exporter des données, un sérialiseur doit être passé en paramètres.
+
+    var myConfig = { serializer: IriSP.serializers.ldt };
+    var myLocalSource = myDirectory.newLocalSource(myConfig);
+
+#### remoteSource ####
+
+Crée ou récupère (si celle-ci existe déjà) une source attachée à une URL. Le sérialiseur est obligatoire.
+
+    var myConfig = { url: "source-data.json", serializer: IriSP.serializers.ldt };
+    var myLocalSource = myDirectory.remoteSource(myConfig);
--- a/src/js/defaults.js	Tue Jun 12 19:44:20 2012 +0200
+++ b/src/js/defaults.js	Wed Jun 20 14:45:30 2012 +0200
@@ -13,7 +13,8 @@
         jwplayer : "jwplayer.js",
         raphael : "raphael-min.js",
         tracemanager : "tracemanager.js",
-        jwPlayerSWF : "player.swf"
+        jwPlayerSWF : "player.swf",
+        json : "json2.js"
     },
     locations : {
         // use to define locations outside defautl_dir
--- a/src/js/init.js	Tue Jun 12 19:44:20 2012 +0200
+++ b/src/js/init.js	Wed Jun 20 14:45:30 2012 +0200
@@ -55,8 +55,13 @@
         .script(IriSP.getLib("underscore"))
         .script(IriSP.getLib("Mustache"))
         .script(IriSP.getLib("jQuery"))
-        .script(IriSP.getLib("swfObject"))
-        .wait()
+        .script(IriSP.getLib("swfObject"));
+    
+    if (typeof JSON == "undefined") {
+        $L.script(IriSP.getLib("json"));
+    }
+    
+    $L.wait()
         .script(IriSP.getLib("jQueryUI"));
 
     if (this.config.player.type === "jwplayer" || this.config.player.type === "auto") {
@@ -175,7 +180,7 @@
 IriSP.Metadataplayer.prototype.configurePopcorn = function() {
     IriSP.log("IriSP.Metadataplayer.prototype.configurePopcorn");
     var pop,
-        ret = this.layoutDivs("video"),
+        ret = this.layoutDivs("video",this.config.player.height || undefined),
         containerDiv = ret[0],
         spacerDiv = ret[1],
         _this = this,
@@ -285,6 +290,10 @@
             pop = new IriSP.PopcornReplacement.allocine("#" + containerDiv, this.config.player);
             break;
         
+        case "mashup-html":
+            pop = new IriSP.PopcornReplacement.htmlMashup("#" + containerDiv, this.config.player, this.videoData);
+            break;
+        
         default:
             pop = undefined;
     };
@@ -296,7 +305,7 @@
     @param widgetName the name of the widget.
     @return an array of the form [createdivId, spacerdivId].
 */
-IriSP.Metadataplayer.prototype.layoutDivs = function(_name) {
+IriSP.Metadataplayer.prototype.layoutDivs = function(_name, _height) {
     if (typeof(_name) === "undefined") {
        _name = "";
     }
@@ -317,6 +326,9 @@
                 position: "relative",
                 clear: "both"
             });
+    if (typeof _height !== "undefined") {
+        divHtml.css("height", _height);
+    }
             
     this.$.append(divHtml);
     this.$.append(spacerHtml);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/js/libs/json2.js	Wed Jun 20 14:45:30 2012 +0200
@@ -0,0 +1,487 @@
+/*
+    json2.js
+    2011-10-19
+
+    Public Domain.
+
+    NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
+
+    See http://www.JSON.org/js.html
+
+
+    This code should be minified before deployment.
+    See http://javascript.crockford.com/jsmin.html
+
+    USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
+    NOT CONTROL.
+
+
+    This file creates a global JSON object containing two methods: stringify
+    and parse.
+
+        JSON.stringify(value, replacer, space)
+            value       any JavaScript value, usually an object or array.
+
+            replacer    an optional parameter that determines how object
+                        values are stringified for objects. It can be a
+                        function or an array of strings.
+
+            space       an optional parameter that specifies the indentation
+                        of nested structures. If it is omitted, the text will
+                        be packed without extra whitespace. If it is a number,
+                        it will specify the number of spaces to indent at each
+                        level. If it is a string (such as '\t' or ' '),
+                        it contains the characters used to indent at each level.
+
+            This method produces a JSON text from a JavaScript value.
+
+            When an object value is found, if the object contains a toJSON
+            method, its toJSON method will be called and the result will be
+            stringified. A toJSON method does not serialize: it returns the
+            value represented by the name/value pair that should be serialized,
+            or undefined if nothing should be serialized. The toJSON method
+            will be passed the key associated with the value, and this will be
+            bound to the value
+
+            For example, this would serialize Dates as ISO strings.
+
+                Date.prototype.toJSON = function (key) {
+                    function f(n) {
+                        // Format integers to have at least two digits.
+                        return n < 10 ? '0' + n : n;
+                    }
+
+                    return this.getUTCFullYear()   + '-' +
+                         f(this.getUTCMonth() + 1) + '-' +
+                         f(this.getUTCDate())      + 'T' +
+                         f(this.getUTCHours())     + ':' +
+                         f(this.getUTCMinutes())   + ':' +
+                         f(this.getUTCSeconds())   + 'Z';
+                };
+
+            You can provide an optional replacer method. It will be passed the
+            key and value of each member, with this bound to the containing
+            object. The value that is returned from your method will be
+            serialized. If your method returns undefined, then the member will
+            be excluded from the serialization.
+
+            If the replacer parameter is an array of strings, then it will be
+            used to select the members to be serialized. It filters the results
+            such that only members with keys listed in the replacer array are
+            stringified.
+
+            Values that do not have JSON representations, such as undefined or
+            functions, will not be serialized. Such values in objects will be
+            dropped; in arrays they will be replaced with null. You can use
+            a replacer function to replace those with JSON values.
+            JSON.stringify(undefined) returns undefined.
+
+            The optional space parameter produces a stringification of the
+            value that is filled with line breaks and indentation to make it
+            easier to read.
+
+            If the space parameter is a non-empty string, then that string will
+            be used for indentation. If the space parameter is a number, then
+            the indentation will be that many spaces.
+
+            Example:
+
+            text = JSON.stringify(['e', {pluribus: 'unum'}]);
+            // text is '["e",{"pluribus":"unum"}]'
+
+
+            text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
+            // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
+
+            text = JSON.stringify([new Date()], function (key, value) {
+                return this[key] instanceof Date ?
+                    'Date(' + this[key] + ')' : value;
+            });
+            // text is '["Date(---current time---)"]'
+
+
+        JSON.parse(text, reviver)
+            This method parses a JSON text to produce an object or array.
+            It can throw a SyntaxError exception.
+
+            The optional reviver parameter is a function that can filter and
+            transform the results. It receives each of the keys and values,
+            and its return value is used instead of the original value.
+            If it returns what it received, then the structure is not modified.
+            If it returns undefined then the member is deleted.
+
+            Example:
+
+            // Parse the text. Values that look like ISO date strings will
+            // be converted to Date objects.
+
+            myData = JSON.parse(text, function (key, value) {
+                var a;
+                if (typeof value === 'string') {
+                    a =
+/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
+                    if (a) {
+                        return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
+                            +a[5], +a[6]));
+                    }
+                }
+                return value;
+            });
+
+            myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
+                var d;
+                if (typeof value === 'string' &&
+                        value.slice(0, 5) === 'Date(' &&
+                        value.slice(-1) === ')') {
+                    d = new Date(value.slice(5, -1));
+                    if (d) {
+                        return d;
+                    }
+                }
+                return value;
+            });
+
+
+    This is a reference implementation. You are free to copy, modify, or
+    redistribute.
+*/
+
+/*jslint evil: true, regexp: true */
+
+/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
+    call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
+    getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
+    lastIndex, length, parse, prototype, push, replace, slice, stringify,
+    test, toJSON, toString, valueOf
+*/
+
+
+// Create a JSON object only if one does not already exist. We create the
+// methods in a closure to avoid creating global variables.
+
+var JSON;
+if (!JSON) {
+    JSON = {};
+}
+
+(function () {
+    'use strict';
+
+    function f(n) {
+        // Format integers to have at least two digits.
+        return n < 10 ? '0' + n : n;
+    }
+
+    if (typeof Date.prototype.toJSON !== 'function') {
+
+        Date.prototype.toJSON = function (key) {
+
+            return isFinite(this.valueOf())
+                ? this.getUTCFullYear()     + '-' +
+                    f(this.getUTCMonth() + 1) + '-' +
+                    f(this.getUTCDate())      + 'T' +
+                    f(this.getUTCHours())     + ':' +
+                    f(this.getUTCMinutes())   + ':' +
+                    f(this.getUTCSeconds())   + 'Z'
+                : null;
+        };
+
+        String.prototype.toJSON      =
+            Number.prototype.toJSON  =
+            Boolean.prototype.toJSON = function (key) {
+                return this.valueOf();
+            };
+    }
+
+    var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+        escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+        gap,
+        indent,
+        meta = {    // table of character substitutions
+            '\b': '\\b',
+            '\t': '\\t',
+            '\n': '\\n',
+            '\f': '\\f',
+            '\r': '\\r',
+            '"' : '\\"',
+            '\\': '\\\\'
+        },
+        rep;
+
+
+    function quote(string) {
+
+// If the string contains no control characters, no quote characters, and no
+// backslash characters, then we can safely slap some quotes around it.
+// Otherwise we must also replace the offending characters with safe escape
+// sequences.
+
+        escapable.lastIndex = 0;
+        return escapable.test(string) ? '"' + string.replace(escapable, function (a) {
+            var c = meta[a];
+            return typeof c === 'string'
+                ? c
+                : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+        }) + '"' : '"' + string + '"';
+    }
+
+
+    function str(key, holder) {
+
+// Produce a string from holder[key].
+
+        var i,          // The loop counter.
+            k,          // The member key.
+            v,          // The member value.
+            length,
+            mind = gap,
+            partial,
+            value = holder[key];
+
+// If the value has a toJSON method, call it to obtain a replacement value.
+
+        if (value && typeof value === 'object' &&
+                typeof value.toJSON === 'function') {
+            value = value.toJSON(key);
+        }
+
+// If we were called with a replacer function, then call the replacer to
+// obtain a replacement value.
+
+        if (typeof rep === 'function') {
+            value = rep.call(holder, key, value);
+        }
+
+// What happens next depends on the value's type.
+
+        switch (typeof value) {
+        case 'string':
+            return quote(value);
+
+        case 'number':
+
+// JSON numbers must be finite. Encode non-finite numbers as null.
+
+            return isFinite(value) ? String(value) : 'null';
+
+        case 'boolean':
+        case 'null':
+
+// If the value is a boolean or null, convert it to a string. Note:
+// typeof null does not produce 'null'. The case is included here in
+// the remote chance that this gets fixed someday.
+
+            return String(value);
+
+// If the type is 'object', we might be dealing with an object or an array or
+// null.
+
+        case 'object':
+
+// Due to a specification blunder in ECMAScript, typeof null is 'object',
+// so watch out for that case.
+
+            if (!value) {
+                return 'null';
+            }
+
+// Make an array to hold the partial results of stringifying this object value.
+
+            gap += indent;
+            partial = [];
+
+// Is the value an array?
+
+            if (Object.prototype.toString.apply(value) === '[object Array]') {
+
+// The value is an array. Stringify every element. Use null as a placeholder
+// for non-JSON values.
+
+                length = value.length;
+                for (i = 0; i < length; i += 1) {
+                    partial[i] = str(i, value) || 'null';
+                }
+
+// Join all of the elements together, separated with commas, and wrap them in
+// brackets.
+
+                v = partial.length === 0
+                    ? '[]'
+                    : gap
+                    ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']'
+                    : '[' + partial.join(',') + ']';
+                gap = mind;
+                return v;
+            }
+
+// If the replacer is an array, use it to select the members to be stringified.
+
+            if (rep && typeof rep === 'object') {
+                length = rep.length;
+                for (i = 0; i < length; i += 1) {
+                    if (typeof rep[i] === 'string') {
+                        k = rep[i];
+                        v = str(k, value);
+                        if (v) {
+                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
+                        }
+                    }
+                }
+            } else {
+
+// Otherwise, iterate through all of the keys in the object.
+
+                for (k in value) {
+                    if (Object.prototype.hasOwnProperty.call(value, k)) {
+                        v = str(k, value);
+                        if (v) {
+                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
+                        }
+                    }
+                }
+            }
+
+// Join all of the member texts together, separated with commas,
+// and wrap them in braces.
+
+            v = partial.length === 0
+                ? '{}'
+                : gap
+                ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}'
+                : '{' + partial.join(',') + '}';
+            gap = mind;
+            return v;
+        }
+    }
+
+// If the JSON object does not yet have a stringify method, give it one.
+
+    if (typeof JSON.stringify !== 'function') {
+        JSON.stringify = function (value, replacer, space) {
+
+// The stringify method takes a value and an optional replacer, and an optional
+// space parameter, and returns a JSON text. The replacer can be a function
+// that can replace values, or an array of strings that will select the keys.
+// A default replacer method can be provided. Use of the space parameter can
+// produce text that is more easily readable.
+
+            var i;
+            gap = '';
+            indent = '';
+
+// If the space parameter is a number, make an indent string containing that
+// many spaces.
+
+            if (typeof space === 'number') {
+                for (i = 0; i < space; i += 1) {
+                    indent += ' ';
+                }
+
+// If the space parameter is a string, it will be used as the indent string.
+
+            } else if (typeof space === 'string') {
+                indent = space;
+            }
+
+// If there is a replacer, it must be a function or an array.
+// Otherwise, throw an error.
+
+            rep = replacer;
+            if (replacer && typeof replacer !== 'function' &&
+                    (typeof replacer !== 'object' ||
+                    typeof replacer.length !== 'number')) {
+                throw new Error('JSON.stringify');
+            }
+
+// Make a fake root object containing our value under the key of ''.
+// Return the result of stringifying the value.
+
+            return str('', {'': value});
+        };
+    }
+
+
+// If the JSON object does not yet have a parse method, give it one.
+
+    if (typeof JSON.parse !== 'function') {
+        JSON.parse = function (text, reviver) {
+
+// The parse method takes a text and an optional reviver function, and returns
+// a JavaScript value if the text is a valid JSON text.
+
+            var j;
+
+            function walk(holder, key) {
+
+// The walk method is used to recursively walk the resulting structure so
+// that modifications can be made.
+
+                var k, v, value = holder[key];
+                if (value && typeof value === 'object') {
+                    for (k in value) {
+                        if (Object.prototype.hasOwnProperty.call(value, k)) {
+                            v = walk(value, k);
+                            if (v !== undefined) {
+                                value[k] = v;
+                            } else {
+                                delete value[k];
+                            }
+                        }
+                    }
+                }
+                return reviver.call(holder, key, value);
+            }
+
+
+// Parsing happens in four stages. In the first stage, we replace certain
+// Unicode characters with escape sequences. JavaScript handles many characters
+// incorrectly, either silently deleting them, or treating them as line endings.
+
+            text = String(text);
+            cx.lastIndex = 0;
+            if (cx.test(text)) {
+                text = text.replace(cx, function (a) {
+                    return '\\u' +
+                        ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+                });
+            }
+
+// In the second stage, we run the text against regular expressions that look
+// for non-JSON patterns. We are especially concerned with '()' and 'new'
+// because they can cause invocation, and '=' because it can cause mutation.
+// But just to be safe, we want to reject all unexpected forms.
+
+// We split the second stage into 4 regexp operations in order to work around
+// crippling inefficiencies in IE's and Safari's regexp engines. First we
+// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
+// replace all simple value tokens with ']' characters. Third, we delete all
+// open brackets that follow a colon or comma or that begin the text. Finally,
+// we look to see that the remaining characters are only whitespace or ']' or
+// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
+
+            if (/^[\],:{}\s]*$/
+                    .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
+                        .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
+                        .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
+
+// In the third stage we use the eval function to compile the text into a
+// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
+// in JavaScript: it can begin a block or an object literal. We wrap the text
+// in parens to eliminate the ambiguity.
+
+                j = eval('(' + text + ')');
+
+// In the optional fourth stage, we recursively walk the new structure, passing
+// each name/value pair to a reviver function for possible transformation.
+
+                return typeof reviver === 'function'
+                    ? walk({'': j}, '')
+                    : j;
+            }
+
+// If the text is not JSON parseable, then a SyntaxError is thrown.
+
+            throw new SyntaxError('JSON.parse');
+        };
+    }
+}());
--- a/src/js/model.js	Tue Jun 12 19:44:20 2012 +0200
+++ b/src/js/model.js	Wed Jun 20 14:45:30 2012 +0200
@@ -1,3 +1,5 @@
+/* TODO: Separate Project-specific data from Source */
+
 /* model.js is where data is stored in a standard form, whatever the serializer */
 
 IriSP.Model = {
@@ -294,11 +296,11 @@
 }
 
 IriSP.Model.Time.prototype.getSeconds = function() {
-    return Math.floor(this.milliseconds / 1000);
+    return this.milliseconds / 1000;
 }
 
 IriSP.Model.Time.prototype.getHMS = function() {
-    var _totalSeconds = Math.abs(this.getSeconds());
+    var _totalSeconds = Math.abs(Math.floor(this.getSeconds()));
     return {
         hours : Math.floor(_totalSeconds / 3600),
         minutes : (Math.floor(_totalSeconds / 60) % 60),
@@ -779,6 +781,9 @@
 }
 
 IriSP.Model.Directory.prototype.remoteSource = function(_properties) {
+    if (typeof _properties !== "object" || typeof _properties.url === "undefined") {
+        throw "Error : IriSP.Model.Directory.remoteSource(configuration): configuration.url is undefined";
+    }
     var _config = IriSP._({ directory: this }).extend(_properties);
     if (typeof this.remoteSources[_properties.url] === "undefined") {
         this.remoteSources[_properties.url] = new IriSP.Model.RemoteSource(_config);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/js/players/player.mashup-html.js	Wed Jun 20 14:45:30 2012 +0200
@@ -0,0 +1,143 @@
+/* To wrap a player the develop should create a new class derived from 
+   the IriSP.PopcornReplacement.player and defining the correct functions */
+
+/** jwplayer player wrapper */
+IriSP.PopcornReplacement.htmlMashup = function(container, options, metadata) {
+    /* appel du parent pour initialiser les structures communes à tous les players */
+    IriSP.PopcornReplacement.player.call(this, container, options);
+    
+    this.mashup = metadata.currentMedia;
+    this.$ = IriSP.jQuery(container);
+    
+    var _w = this.$.width(),
+        _h = this.$.height(),
+        _this = this;
+    
+    IriSP._(metadata.currentMedia.medias).each(function(_media) {
+        var _tmpId = Popcorn.guid("video"),
+            _videoEl = IriSP.jQuery('<video>');
+            
+        _videoEl
+            .attr({
+                src : _media.video,
+                id : _tmpId,
+                width : _w,
+                height : _h
+            })
+            .css({
+                position: "absolute",
+                top: 0,
+                left: 0
+            });
+
+        _this.$.append(_videoEl);
+        _media.videoEl = _videoEl;
+        _media.popcorn = Popcorn("#" + _tmpId);
+        _media.popcorn.on("timeupdate", function() {
+            if (!_this.media.paused && _media === _this.currentMedia) {
+                var _time = _media.popcorn.currentTime();
+             //   var _status = "Timeupdate from " + _media.id + " at time " + _time;
+                if ( _time < _this.segmentEnd ) {
+                    if ( _time >= _this.segmentBegin ) {
+                        _this.timecode = _time - _this.timedelta;
+                  //      _status += " within segment";
+                    } else {
+                        _this.timecode = _this.segmentBegin - _this.timedelta;
+                        _media.popcorn.currentTime(_this.segmentBegin);
+                   //     _status += " before segment begin";
+                    }
+                } else {
+                    _this.timecode = _this.segmentEnd - _this.timedelta;
+                    _media.popcorn.pause();
+                    _this.changeCurrentAnnotation();
+                 //   _status += " after segment end";
+                }
+            /*    _status += ", translated to " + _this.timecode;
+                console.log(_status); */
+                _this.trigger("timeupdate");
+            }
+        });
+    });
+    
+    this.timecode = 0;
+  
+  /* Définition des fonctions de l'API */
+    this.playerFns = {
+        play: function() {
+            _this.changeCurrentAnnotation();
+        },
+        pause: function() {
+            _this.currentMedia.popcorn.pause();
+        },
+        getPosition: function() {
+            return _this.timecode;
+        },
+        seek: function(pos) {
+            _this.timecode = pos;
+            _this.changeCurrentAnnotation();
+        },
+        getMute: function() {
+            return
+                typeof _this.currentMedia !== "undefined"
+                ? _this.currentMedia.popcorn.muted()
+                : false;
+        },
+        setMute: function(p) {
+            var _mute = !!p;
+            for (var _i = 0; _i < _this.mashup.medias.length; _i++) {
+                _this.mashup.medias[_i].popcorn.muted(_mute);
+            }
+        },
+        getVolume: function() {
+            return
+                typeof _this.currentMedia !== "undefined"
+                ? _this.currentMedia.popcorn.volume()
+                : .5;
+        },
+        setVolume: function(_vol) {
+            for (var _i = 0; _i < _this.mashup.medias.length; _i++) {
+                _this.mashup.medias[_i].popcorn.volume(_vol);
+            }
+        }
+    }
+/*
+    options.events = this.callbacks;
+
+    _player.setup(options);
+    */
+};
+
+IriSP.PopcornReplacement.htmlMashup.prototype = new IriSP.PopcornReplacement.player("", {});
+
+IriSP.PopcornReplacement.htmlMashup.prototype.changeCurrentAnnotation = function() {
+    var _annotation = this.mashup.getAnnotationAtTime( 1000 * this.timecode );
+    if (typeof _annotation == "undefined") {
+        if (typeof this.currentMedia !== "undefined") {
+            this.currentMedia.popcorn.pause();
+            this.media.paused = true;
+        }
+        return;
+    }
+    if (_annotation !== this.currentAnnotation) {
+        this.currentAnnotation = _annotation;
+        this.segmentBegin = this.currentAnnotation.annotation.begin.getSeconds();
+        this.segmentEnd = this.currentAnnotation.annotation.end.getSeconds();
+        this.timedelta = this.segmentBegin - this.currentAnnotation.begin.getSeconds();
+        this.currentMedia = this.currentAnnotation.getMedia();
+        
+        for (var _i = 0; _i < this.mashup.medias.length; _i++) {
+            if (this.mashup.medias[_i].id !== this.currentMedia.id) {
+                this.mashup.medias[_i].videoEl.hide();
+                this.mashup.medias[_i].popcorn.pause();
+            } else {
+                this.mashup.medias[_i].videoEl.show();
+            }
+        }
+    }
+    if (this.currentMedia.popcorn.readyState()) {
+        this.currentMedia.popcorn.currentTime(this.timecode + this.timedelta);
+    }
+    if (!this.media.paused) {
+        this.currentMedia.popcorn.play();
+    }
+}
--- a/src/js/pop.js	Tue Jun 12 19:44:20 2012 +0200
+++ b/src/js/pop.js	Wed Jun 20 14:45:30 2012 +0200
@@ -33,7 +33,7 @@
                         a segment (similar to the popcorn.code plugin). */
   
   this._options = options;
-                          
+
 };
 
 IriSP.PopcornReplacement.player.prototype.listen = function(msg, callback) {
@@ -43,6 +43,8 @@
   this.msgPump[msg].push(callback);
 };
 
+IriSP.PopcornReplacement.player.prototype.on = IriSP.PopcornReplacement.player.prototype.listen;
+
 IriSP.PopcornReplacement.player.prototype.trigger = function(msg, params) {
   if (!this.msgPump.hasOwnProperty(msg))
     return;
@@ -55,6 +57,8 @@
 
 };
 
+IriSP.PopcornReplacement.player.prototype.emit = IriSP.PopcornReplacement.player.prototype.trigger;
+
 IriSP.PopcornReplacement.player.prototype.guid = function(prefix) {
   var str = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
       var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
@@ -98,16 +102,13 @@
 IriSP.PopcornReplacement.player.prototype.play = function() {
   this.media.paused = false;
   this.trigger("play");
-  //IriSP.PopcornReplacement.trigger("playing");
   this.playerFns.play();
 };
     
 IriSP.PopcornReplacement.player.prototype.pause = function() {
-//  if ( !this.media.paused ) {
     this.media.paused = true;
     this.trigger( "pause" );
     this.playerFns.pause();
-//  }
 };
 
 IriSP.PopcornReplacement.player.prototype.muted = function(val) {
@@ -147,7 +148,13 @@
     return _vol;
 };
 
-IriSP.PopcornReplacement.player.prototype.mute = IriSP.PopcornReplacement.player.prototype.muted;
+IriSP.PopcornReplacement.player.prototype.mute = function() {
+    this.muted(true);
+}
+
+IriSP.PopcornReplacement.player.prototype.unmute = function() {
+    this.muted(false);
+}
 
 IriSP.PopcornReplacement.player.prototype.code = function(options) {
   this.__codes.push(options);
--- a/src/js/serializers/ldt.js	Tue Jun 12 19:44:20 2012 +0200
+++ b/src/js/serializers/ldt.js	Wed Jun 20 14:45:30 2012 +0200
@@ -25,6 +25,10 @@
                 _res.title = _data.meta["dc:title"];
                 _res.description = _data.meta["dc:description"];
                 _res.setDuration(_data.meta["dc:duration"]);
+                _res.url = _data.meta.url;
+                if (typeof _data.meta.img !== "undefined" && _data.meta.img.src !== "undefined") {
+                    _res.thumbnail = _data.meta.img.src;
+                }
                 return _res;        
             },
             serializer : function(_data, _source) {
--- a/src/widgets/Controller.js	Tue Jun 12 19:44:20 2012 +0200
+++ b/src/widgets/Controller.js	Wed Jun 20 14:45:30 2012 +0200
@@ -196,7 +196,7 @@
 };
 
 IriSP.Widgets.Controller.prototype.muteHandler = function() {
-    this.player.popcorn.mute(!this.player.popcorn.muted());
+    this.player.popcorn.muted(!this.player.popcorn.muted());
 };
 
 IriSP.Widgets.Controller.prototype.volumeUpdater = function() {
--- a/src/widgets/MediaList.css	Tue Jun 12 19:44:20 2012 +0200
+++ b/src/widgets/MediaList.css	Wed Jun 20 14:45:30 2012 +0200
@@ -83,4 +83,20 @@
     margin: 2px 0 2px 62px;
     font-size: 10px;
     color: #666666;
-}
\ No newline at end of file
+}
+
+.Ldt-MediaList-Now-MediaView {
+    clear: both; width: 100%; height: 12px; margin: 5px 0 0; overflow: hidden; position: relative;
+}
+
+.Ldt-MediaList-Other-MediaView {
+    clear: both; width: 100%; height: 6px; margin: 5px 0 0; overflow: hidden; position: relative;
+}
+
+.Ldt-MediaList-MediaView-Background {
+    position: absolute; top: 0; left: 0; width: 100%; height: 100%; filter: alpha(opacity=50); opacity: 0.5; background: #999999;
+}
+
+.Ldt-MediaList-Segment {
+    position: absolute; top: 0; height: 100%; border-style: none solid; margin-left: -1px; border-color: #ffffff; border-width: 1px;
+}
--- a/src/widgets/MediaList.js	Tue Jun 12 19:44:20 2012 +0200
+++ b/src/widgets/MediaList.js	Wed Jun 20 14:45:30 2012 +0200
@@ -20,7 +20,8 @@
 
 IriSP.Widgets.MediaList.prototype.defaults = {
     default_thumbnail : "http://ldt.iri.centrepompidou.fr/static/site/ldt/css/imgs/video_sequence.png",
-    media_url_template : "http://ldt.iri.centrepompidou.fr/ldtplatform/ldt/front/player/{{media}}/"
+    media_url_template : "http://ldt.iri.centrepompidou.fr/ldtplatform/ldt/front/player/{{media}}/",
+    default_color : "#000080"
 };
 
 IriSP.Widgets.MediaList.prototype.template =
@@ -29,14 +30,19 @@
     + '<div class="Ldt-MediaList-Now-ThumbContainer"><a href="" target="_blank">'
     + '<img class="Ldt-MediaList-Now-Thumbnail" src="" /></a></div>'
     + '<h3 class="Ldt-MediaList-Now-Title"><a href="" target="_blank"></a></h3>'
-    + '<p class="Ldt-MediaList-Now-Description"></p></div></div>'
+    + '<p class="Ldt-MediaList-Now-Description"></p><div class="Ldt-MediaList-Now-MediaView"></div></div></div>'
     + '<div class="Ldt-MediaList-Other"><h2></h2><hr /><ul class="Ldt-MediaList-OtherList"></ul></div>';
 
+IriSP.Widgets.MediaList.prototype.mediaViewTemplate =
+    '<div class="Ldt-MediaList-MediaView-Background"></div>{{#segments}}<div class="Ldt-MediaList-Segment" style="background: {{color}}; left: {{left}}px; width: {{width}}px;"></div>{{/segments}}';
+
 IriSP.Widgets.MediaList.prototype.mediaTemplate =
     '<li class="Ldt-MediaList-OtherList-li"><div class="Ldt-MediaList-Other-ThumbContainer"><a href="{{url}}" target="_blank">'
     + '<img class="Ldt-MediaList-Other-Thumbnail" src="{{thumbnail}}" /></a></div>'
     + '<h3 class="Ldt-MediaList-Other-Title"><a href="{{url}}" target="_blank">{{title}}</a></h3>'
-    + '<p class="Ldt-MediaList-Other-Description">{{description}}</p></li>'
+    + '<p class="Ldt-MediaList-Other-Description">{{description}}</p><div class="Ldt-MediaList-Other-MediaView">'
+    + IriSP.Widgets.MediaList.prototype.mediaViewTemplate + '</div></li>';
+
 
 IriSP.Widgets.MediaList.prototype.onSearch = function(searchString) {
     this.searchString = typeof searchString !== "undefined" ? searchString : '';
@@ -57,6 +63,23 @@
     this.redraw();
 };
 
+IriSP.Widgets.MediaList.prototype.getSegments = function(_media) {
+    var _this = this,
+        _scale = this.$.width()/_media.duration.milliseconds;
+    return this.getWidgetAnnotations()
+        .filter(function(_annotation) {
+            return _annotation.getMedia().id == _media.id;
+        })
+        .map(function(_a) {
+            var _annotation = ( _a.type = "mashedAnnotation" ? _a.annotation : _a );
+            return {
+                left: _scale * _annotation.begin,
+                width: _scale * (_annotation.end - _annotation.begin),
+                color: ( typeof _annotation.color !== "undefined" && _annotation.color ? _annotation.color : _this.default_color )
+            }
+        })
+}
+
 IriSP.Widgets.MediaList.prototype.redraw = function(_media) {
     if (typeof _media !== "undefined") {
         this.$.find('.Ldt-MediaList-Other h2').html(this.l10n.other_media);
@@ -69,6 +92,10 @@
                     media: _media.id
                 });
         this.$.find('.Ldt-MediaList-NowContainer a').attr("href", _url);
+        var _mediaView = Mustache.to_html( this.mediaViewTemplate, {
+            segments: this.getSegments(_media)
+        });
+        this.$.find('.Ldt-MediaList-Now-MediaView').html(_mediaView);
     } else {
         this.$.find('.Ldt-MediaList-Other h2').html(this.l10n.all_media);
         this.$.find('.Ldt-MediaList-NowPlaying').hide();
@@ -87,7 +114,8 @@
                         media: _media.id
                     }),
                 title: _media.title,
-                description: _media.description
+                description: _media.description,
+                segments: _this.getSegments(_media)
             })
         }).join("");
         this.$.find('.Ldt-MediaList-OtherList').html(_html);
Binary file test/mashup/bab_files/player_bab_ldt.swf has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/mashup/moon.htm	Wed Jun 20 14:45:30 2012 +0200
@@ -0,0 +1,99 @@
+<!doctype html>
+<html>
+    <head>
+        <title>Preuve de concept Mashup</title>
+        <link rel="stylesheet" type="text/css" href="style.css" />
+        <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
+        <script type="text/javascript" src="../metadataplayer/LdtPlayer-core.js" type="text/javascript"></script>
+    </head>
+    <body>
+        <div class="main-container">
+            <div class="header">
+                <h1>Hash Cut #</h1>
+            </div>
+            <div class="steps">
+                <h2>Créer un Hash-cut en 3 étapes&nbsp;:</h2>
+                <div class="steps-frame">
+                    <div class="step">
+                        <div class="step-icon step-1"></div>
+                        <div class="step-title">S'inscrire et<br />créer un projet</div>
+                    </div>
+                    <div class="step-separator"></div>
+                    <div class="step">
+                        <div class="step-icon step-2"></div>
+                        <div class="step-title">Découper et<br />Assembler</div>
+                    </div>
+                    <div class="step-separator"></div>
+                    <div class="step active">
+                        <div class="step-icon step-3"></div>
+                        <div class="step-title">Partager et<br />regarder&nbsp;!</div>
+                    </div>
+                </div>
+            </div>
+            <div class="colgauche">
+                <h2>Mon HashCut</h2>
+                <hr />
+                <div id="LdtPlayer"></div>
+            </div>
+            <div class="coldroite">
+                <div id="mediaList"></div>
+            </div>
+            <div class="footer">
+                <hr />
+                <p style="text-align: right;">&copy; IRI 2012</p>
+            </div>
+        </div>
+    <script type="text/javascript">
+
+IriSP.libFiles.defaultDir = "../libs/";
+IriSP.widgetsDir = "../metadataplayer";
+IriSP.language = 'fr';
+var _metadata = {
+    url: 'moon/moon.json',
+    format: 'ldt'
+};
+var _config = {            
+    gui: {
+        width : 630,
+        container : 'LdtPlayer',
+        default_options: {
+            metadata: _metadata
+        },
+        css : '../metadataplayer/LdtPlayer-core.css',
+        widgets: [
+            { type: "Slider" },
+            { type: "Controller" },
+            {
+                type: "Segments",
+                annotation_type: false
+            },
+            { type: "Arrow" },
+            {
+                type: "Annotation",
+                annotation_type: false
+            },
+            {
+                type: "MediaList",
+                container: "mediaList"
+            },
+            { type: "Mediafragment" }
+        ]
+    },
+    player:{
+        type:'mashup-html',
+        live: true, 
+        height: 350,
+        width: 630, 
+        provider: "rtmp",
+        mashup_swf : "bab_files/player_bab_ldt.swf",
+        mashup_xml : "moon/moon.xml",
+        autostart: true,
+        metadata: _metadata
+    }
+};
+
+_myPlayer = new IriSP.Metadataplayer(_config);
+
+    </script>
+    </body>
+</html>
\ No newline at end of file
Binary file test/mashup/moon/apollo.jpg has changed
Binary file test/mashup/moon/atlas.jpg has changed
Binary file test/mashup/moon/melies.jpg has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/mashup/moon/moon.json	Wed Jun 20 14:45:30 2012 +0200
@@ -0,0 +1,406 @@
+{
+    "views": null,
+    "tags": null,
+    "lists": null,
+    "medias": [
+        {
+            "origin": "0",
+            "url": "moon/melies.webm",
+            "http://advene.liris.cnrs.fr/ns/frame_of_reference/ms": "o=0",
+            "meta": {
+                "dc:contributor": "IRI",
+                "dc:created": "2011-09-19T13:17:56.656743",
+                "dc:duration": 674000,
+                "dc:creator": "IRI",
+                "dc:created.contents": "2012-02-13",
+                "dc:title": "Le Voyage dans la Lune de Méliès",
+                "dc:creator.contents": "IRI",
+                "dc:modified": "2012-02-13T11:55:33.052583",
+                "dc:description": "Georges Méliès, 1902. Domaine public.",
+                "url": "http://en.wikipedia.org/wiki/File:Le_Voyage_dans_la_Lune_%28Georges_M%C3%A9li%C3%A8s,_1902%29.ogv",
+                "img": {
+                    "src": "moon/melies.jpg"
+                }
+            },
+            "id": "media-melies",
+            "unit": "ms"
+        },
+        {
+            "origin": "0",
+            "url": "moon/juno.webm",
+            "http://advene.liris.cnrs.fr/ns/frame_of_reference/ms": "o=0",
+            "meta": {
+                "dc:contributor": "IRI",
+                "dc:created": "2011-09-19T13:17:56.656743",
+                "dc:duration": 130000,
+                "dc:creator": "IRI",
+                "dc:created.contents": "2012-02-13",
+                "dc:title": "Launch of Juno!, NASA",
+                "dc:creator.contents": "IRI",
+                "dc:modified": "2012-02-13T11:55:33.052583",
+                "dc:description": "An Atlas V rocket lofted the Juno spacecraft toward Jupiter from Space Launch Complex-41. The 4-ton Juno spacecraft will take five years to reach Jupiter on a mission to study its structure and decipher its history. NASA, 2011. Domaine public.",
+                "url": "http://www.nasa.gov/multimedia/videogallery/index.html?media_id=105049051",
+                "img": {
+                    "src": "moon/atlas.jpg"
+                }
+            },
+            "id": "media-atlas",
+            "unit": "ms"
+        },
+        {
+            "origin": "0",
+            "url": "moon/apollo.webm",
+            "http://advene.liris.cnrs.fr/ns/frame_of_reference/ms": "o=0",
+            "meta": {
+                "dc:contributor": "IRI",
+                "dc:created": "2011-09-19T13:17:56.656743",
+                "dc:duration": 137000,
+                "dc:creator": "IRI",
+                "dc:created.contents": "2012-02-13",
+                "dc:title": "Apollo 11 Overview, NASA",
+                "dc:creator.contents": "IRI",
+                "dc:modified": "2012-02-13T11:55:33.052583",
+                "dc:description": "Video highlights from the historic first manned landing on the moon, during the Apollo 11 mission in July 1969. NASA, 2011. Domaine public.",
+                "url": "http://www.nasa.gov/multimedia/videogallery/index.html?media_id=11463015",
+                "img": {
+                    "src": "moon/apollo.jpg"
+                }
+            },
+            "id": "media-apollo",
+            "unit": "ms"
+        }
+    ],
+    "meta": {
+        "dc:contributor": "admin",
+        "dc:created": "2012-05-11T16:26:53.787298",
+        "dc:creator": "admin",
+        "main_media": {
+            "id-ref": "mashup-001"
+        },
+        "dc:description": "",
+        "dc:title": "To the Moon, feat. Méliès + NASA",
+        "id": "5afd8bbe-9b75-11e1-9e5d-00145ea4a2be",
+        "dc:modified": "2012-05-11T16:37:30.246796"
+    },
+    "annotations": [
+        {
+            "content": {
+                "mimetype": "application/x-ldt-structured",
+                "description": "Le Voyage dans la Lune, Segment 1",
+                "img": {
+                    "src": ""
+                },
+                "title": "La capsule lunaire est chargée dans le canon permettant son lancement",
+                "color": "16763904",
+                "polemics": [],
+                "audio": {
+                    "mimetype": "audio/mp3",
+                    "src": "",
+                    "href": null
+                }
+            },
+            "begin": 301000,
+            "end": 306000,
+            "meta": {
+                "dc:contributor": "perso",
+                "id-ref": "decoupage-melies",
+                "dc:created": "2012-05-11T15:08:00.348480",
+                "dc:modified": "2012-05-11T15:08:00.348480",
+                "dc:creator": "perso"
+            },
+            "tags": [],
+            "color": "10485760",
+            "media": "media-melies",
+            "id": "segment-melies-001"
+        },
+        {
+            "content": {
+                "mimetype": "application/x-ldt-structured",
+                "description": "Le Voyage dans la Lune, Segment 2",
+                "img": {
+                    "src": ""
+                },
+                "title": "Ordre de mise à feu du canon propulseur",
+                "color": "16763904",
+                "polemics": [],
+                "audio": {
+                    "mimetype": "audio/mp3",
+                    "src": "",
+                    "href": null
+                }
+            },
+            "begin": 314500,
+            "end": 316000,
+            "meta": {
+                "dc:contributor": "perso",
+                "id-ref": "decoupage-melies",
+                "dc:created": "2012-05-11T15:08:00.348480",
+                "dc:modified": "2012-05-11T15:08:00.348480",
+                "dc:creator": "perso"
+            },
+            "tags": [],
+            "color": "10485760",
+            "media": "media-melies",
+            "id": "segment-melies-002"
+        },
+        {
+            "content": {
+                "mimetype": "application/x-ldt-structured",
+                "description": "Le Voyage dans la Lune, Segment 3",
+                "img": {
+                    "src": ""
+                },
+                "title": "La Lune se rapproche et dévoile son visage",
+                "color": "16763904",
+                "polemics": [],
+                "audio": {
+                    "mimetype": "audio/mp3",
+                    "src": "",
+                    "href": null
+                }
+            },
+            "begin": 344000,
+            "end": 347000,
+            "meta": {
+                "dc:contributor": "perso",
+                "id-ref": "decoupage-melies",
+                "dc:created": "2012-05-11T15:08:00.348480",
+                "dc:modified": "2012-05-11T15:08:00.348480",
+                "dc:creator": "perso"
+            },
+            "tags": [],
+            "color": "10485760",
+            "media": "media-melies",
+            "id": "segment-melies-003"
+        },
+        {
+            "content": {
+                "mimetype": "application/x-ldt-structured",
+                "description": "Le Voyage dans la Lune, Segment 4",
+                "img": {
+                    "src": ""
+                },
+                "title": "Alunissage et sortie de l'équipage",
+                "color": "16763904",
+                "polemics": [],
+                "audio": {
+                    "mimetype": "audio/mp3",
+                    "src": "",
+                    "href": null
+                }
+            },
+            "begin": 348500,
+            "end": 358000,
+            "meta": {
+                "dc:contributor": "perso",
+                "id-ref": "decoupage-melies",
+                "dc:created": "2012-05-11T15:08:00.348480",
+                "dc:modified": "2012-05-11T15:08:00.348480",
+                "dc:creator": "perso"
+            },
+            "tags": [],
+            "color": "10485760",
+            "media": "media-melies",
+            "id": "segment-melies-004"
+        },
+        {
+            "content": {
+                "mimetype": "application/x-ldt-structured",
+                "description": "Lancement de la sonde Juno, Segment 1",
+                "img": {
+                    "src": ""
+                },
+                "title": "Décompte du décollage de la fusée Atlas V",
+                "color": "16763904",
+                "polemics": [],
+                "audio": {
+                    "mimetype": "audio/mp3",
+                    "src": "",
+                    "href": null
+                }
+            },
+            "begin": 7000,
+            "end": 11000,
+            "meta": {
+                "dc:contributor": "perso",
+                "id-ref": "decoupage-atlas",
+                "dc:created": "2012-05-11T15:08:00.348480",
+                "dc:modified": "2012-05-11T15:08:00.348480",
+                "dc:creator": "perso"
+            },
+            "tags": [],
+            "color": "32768",
+            "media": "media-atlas",
+            "id": "segment-atlas-001"
+        },
+        {
+            "content": {
+                "mimetype": "application/x-ldt-structured",
+                "description": "Lancement de la sonde Juno, Segment 2",
+                "img": {
+                    "src": ""
+                },
+                "title": "La fusée Atlas V décolle",
+                "color": "16763904",
+                "polemics": [],
+                "audio": {
+                    "mimetype": "audio/mp3",
+                    "src": "",
+                    "href": null
+                }
+            },
+            "begin": 16000,
+            "end": 19000,
+            "meta": {
+                "dc:contributor": "perso",
+                "id-ref": "decoupage-atlas",
+                "dc:created": "2012-05-11T15:08:00.348480",
+                "dc:modified": "2012-05-11T15:08:00.348480",
+                "dc:creator": "perso"
+            },
+            "tags": [],
+            "color": "32768",
+            "media": "media-atlas",
+            "id": "segment-atlas-002"
+        },
+        {
+            "content": {
+                "mimetype": "application/x-ldt-structured",
+                "description": "Lancement de la sonde Juno, Segment 3",
+                "img": {
+                    "src": ""
+                },
+                "title": "La fusée Atlas V en vol",
+                "color": "16763904",
+                "polemics": [],
+                "audio": {
+                    "mimetype": "audio/mp3",
+                    "src": "",
+                    "href": null
+                }
+            },
+            "begin": 35000,
+            "end": 38000,
+            "meta": {
+                "dc:contributor": "perso",
+                "id-ref": "decoupage-atlas",
+                "dc:created": "2012-05-11T15:08:00.348480",
+                "dc:modified": "2012-05-11T15:08:00.348480",
+                "dc:creator": "perso"
+            },
+            "tags": [],
+            "color": "32768",
+            "media": "media-atlas",
+            "id": "segment-atlas-003"
+        },
+        {
+            "content": {
+                "mimetype": "application/x-ldt-structured",
+                "description": "Mission Apollo 11, Segment 1",
+                "img": {
+                    "src": ""
+                },
+                "title": "Décollage du lanceur Saturn V",
+                "color": "16763904",
+                "polemics": [],
+                "audio": {
+                    "mimetype": "audio/mp3",
+                    "src": "",
+                    "href": null
+                }
+            },
+            "begin": 20000,
+            "end": 23000,
+            "meta": {
+                "dc:contributor": "perso",
+                "id-ref": "decoupage-apollo",
+                "dc:created": "2012-05-11T15:08:00.348480",
+                "dc:modified": "2012-05-11T15:08:00.348480",
+                "dc:creator": "perso"
+            },
+            "tags": [],
+            "color": "16763904",
+            "media": "media-apollo",
+            "id": "segment-apollo-001"
+        },
+        {
+            "content": {
+                "mimetype": "application/x-ldt-structured",
+                "description": "Mission Apollo 11, Segment 2",
+                "img": {
+                    "src": ""
+                },
+                "title": "'One small step for man, one giant leap for mankind'",
+                "color": "16763904",
+                "polemics": [],
+                "audio": {
+                    "mimetype": "audio/mp3",
+                    "src": "",
+                    "href": null
+                }
+            },
+            "begin": 62500,
+            "end": 75500,
+            "meta": {
+                "dc:contributor": "perso",
+                "id-ref": "decoupage-apollo",
+                "dc:created": "2012-05-11T15:08:00.348480",
+                "dc:modified": "2012-05-11T15:08:00.348480",
+                "dc:creator": "perso"
+            },
+            "tags": [],
+            "color": "16763904",
+            "media": "media-apollo",
+            "id": "segment-apollo-002"
+        }
+    ],
+    "annotation-types": [
+        {
+            "dc:contributor": "perso",
+            "dc:creator": "perso",
+            "dc:title": "Segments du Voyage sur la Lune",
+            "id": "decoupage-melies",
+            "dc:created": "2012-05-11T15:08:00.348480",
+            "dc:description": "",
+            "dc:modified": "2012-05-11T15:08:00.348480"
+        },
+        {
+            "dc:contributor": "perso",
+            "dc:creator": "perso",
+            "dc:title": "Segments du Lancement de Juno",
+            "id": "decoupage-atlas",
+            "dc:created": "2012-05-11T15:08:00.348480",
+            "dc:description": "",
+            "dc:modified": "2012-05-11T15:08:00.348480"
+        },
+        {
+            "dc:contributor": "perso",
+            "dc:creator": "perso",
+            "dc:title": "Segments d'Apollo 11",
+            "id": "decoupage-apollo",
+            "dc:created": "2012-05-11T15:08:00.348480",
+            "dc:description": "",
+            "dc:modified": "2012-05-11T15:08:00.348480"
+        }
+    ],
+    "mashups": [
+        {
+            "id": "mashup-001",
+            "meta": {
+                "dc:title": "Example mashup",
+                "dc:description": "This is an example mashup"
+            },
+            "segments": [
+                "segment-melies-001",
+                "segment-atlas-001",
+                "segment-melies-002",
+                "segment-apollo-001",
+                "segment-atlas-002",
+                "segment-melies-003",
+                "segment-atlas-003",
+                "segment-melies-004",
+                "segment-apollo-002"
+            ]
+        }
+    ]
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/mp4video.htm	Wed Jun 20 14:45:30 2012 +0200
@@ -0,0 +1,21 @@
+<!doctype html>
+<html>
+
+    <head>
+        <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
+        <title>Metadataplayer test with HTML5 / OGG Video</title>
+        <link href='http://fonts.googleapis.com/css?family=Open+Sans:400,700,400italic,700italic' rel='stylesheet' type='text/css'>
+        <link href='test.css' rel='stylesheet' type='text/css'>
+        <script type="text/javascript" src="metadataplayer/LdtPlayer-core.js" type="text/javascript"></script>
+        <script type="text/javascript" src="test-config.js" type="text/javascript"></script>
+    </head>
+
+    <body>
+        <h1>Metadataplayer test with HTML5 / H.264 Video - Does not work with Firefox and Chrome</h1>
+        <div id="LdtPlayer"></div>
+        <div id="AnnotationsListContainer"></div>
+        <script type="text/javascript">
+            testConfig('json/ldt-ogv.json', true, "trailer.mp4", "html5");
+        </script>
+    </body>
+</html>
--- a/test/oggvideo.htm	Tue Jun 12 19:44:20 2012 +0200
+++ b/test/oggvideo.htm	Wed Jun 20 14:45:30 2012 +0200
@@ -11,7 +11,7 @@
     </head>
 
     <body>
-        <h1>Metadataplayer test with HTML5 / OGG Video</h1>
+        <h1>Metadataplayer test with HTML5 / OGG Video - Does not work with IE and Safari</h1>
         <div id="LdtPlayer"></div>
         <div id="AnnotationsListContainer"></div>
         <script type="text/javascript">
--- a/test/test-config.js	Tue Jun 12 19:44:20 2012 +0200
+++ b/test/test-config.js	Wed Jun 20 14:45:30 2012 +0200
@@ -1,4 +1,4 @@
-function testConfig(_urlMetadata, _useLocalBuild) {
+function testConfig(_urlMetadata, _useLocalBuild, _video, _playerType) {
     document.getElementById('LdtPlayer').innerHTML = '';
     _useLocalBuild = (typeof _useLocalBuild !== "undefined" && _useLocalBuild)
     IriSP.libFiles.defaultDir = _useLocalBuild ? "libs/" : "../src/js/libs/";
@@ -31,12 +31,12 @@
                     type: "AnnotationsList",
                     container: "AnnotationsListContainer"
                 },
-                { type: "Mediafragment"},
-                {
+                { type: "Mediafragment"}
+/*                {
                     type: "Trace",
                     default_subject: "tests-iri",
                     js_console: true
-                }
+            } */
             ]
         },
         player:{
@@ -49,6 +49,12 @@
             metadata: _metadata
         }
     };
+    if (typeof _playerType != "undefined") {
+        _config.player.type = _playerType;
+    }
+    if (typeof _video != "undefined") {
+        _config.player.video = _video;
+    }
     
     return new IriSP.Metadataplayer(_config);
 }
\ No newline at end of file