Update Metadataplayer
authorveltr
Thu, 27 Sep 2012 12:23:41 +0200
changeset 15 ed7ac7e94090
parent 14 94e058cf84de
child 16 5135ccbf5a5f
Update Metadataplayer
metadataplayer/audiomp3.htm
metadataplayer/dailymotion.htm
metadataplayer/docs/architecture.fr.md
metadataplayer/docs/data-api.fr.md
metadataplayer/docs/data-api.md
metadataplayer/docs/integration.fr.md
metadataplayer/docs/widget-api.fr.md
metadataplayer/index.htm
metadataplayer/jwplayer.htm
metadataplayer/libs/backbone-relational.js
metadataplayer/libs/backbone.js
metadataplayer/libs/jquery.mousewheel.min.js
metadataplayer/libs/paper.js
metadataplayer/libs/player.swf
metadataplayer/libs/record_mic.swf
metadataplayer/libs/renkan-publish.js
metadataplayer/libs/underscore-min.js
metadataplayer/mashup/moon.htm
metadataplayer/mashup/player-html.htm
metadataplayer/mashup/player-local-html.htm
metadataplayer/mashup/player-local.htm
metadataplayer/mashup/player.htm
metadataplayer/metadataplayer/Annotation.js
metadataplayer/metadataplayer/AnnotationsList.css
metadataplayer/metadataplayer/AnnotationsList.js
metadataplayer/metadataplayer/Arrow.js
metadataplayer/metadataplayer/AutoPlayer.js
metadataplayer/metadataplayer/Controller.css
metadataplayer/metadataplayer/Controller.js
metadataplayer/metadataplayer/CreateAnnotation.css
metadataplayer/metadataplayer/CreateAnnotation.js
metadataplayer/metadataplayer/DailymotionPlayer.js
metadataplayer/metadataplayer/JwpPlayer.js
metadataplayer/metadataplayer/KnowledgeConcierge.css
metadataplayer/metadataplayer/KnowledgeConcierge.js
metadataplayer/metadataplayer/LdtPlayer-core.js
metadataplayer/metadataplayer/MashupPlayer.js
metadataplayer/metadataplayer/MediaList.js
metadataplayer/metadataplayer/Mediafragment.js
metadataplayer/metadataplayer/Polemic.js
metadataplayer/metadataplayer/PopcornPlayer.js
metadataplayer/metadataplayer/Renkan.css
metadataplayer/metadataplayer/Renkan.js
metadataplayer/metadataplayer/Segments.js
metadataplayer/metadataplayer/Slice.css
metadataplayer/metadataplayer/Slice.js
metadataplayer/metadataplayer/Slider.css
metadataplayer/metadataplayer/Slider.js
metadataplayer/metadataplayer/Slideshare.js
metadataplayer/metadataplayer/Sparkline.js
metadataplayer/metadataplayer/Tagcloud.js
metadataplayer/metadataplayer/Tagger.css
metadataplayer/metadataplayer/Tagger.js
metadataplayer/metadataplayer/Tooltip.js
metadataplayer/metadataplayer/Trace.js
metadataplayer/metadataplayer/Tweet.js
metadataplayer/metadataplayer/img/pinstripe-grey.png
metadataplayer/metadataplayer/img/voiceannotation.png
metadataplayer/metadataplayer/img/zoombuttons.png
metadataplayer/oggvideo.htm
metadataplayer/test-config.js
metadataplayer/youtube.htm
--- a/metadataplayer/audiomp3.htm	Fri Aug 31 17:43:31 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,64 +0,0 @@
-<!doctype html>
-<html>
-
-    <head>
-        <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
-        <title>Metadataplayer test with HTML5 / MP3 Audio</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 / MP3 Audio (NOT SUPPORTED BY CHROME &amp; FIREFOX)</h1>
-        <div id="LdtPlayer"></div>
-        <div id="AnnotationsListContainer"></div>
-        <script type="text/javascript">
-    IriSP.jwplayer_swf_path = "player.swf";
-    IriSP.libFiles.defaultDir = "libs/";
-    IriSP.widgetsDir = "metadataplayer";
-    var _metadata = {
-        url: 'json/ldt-ogv.json',
-        format: 'ldt'
-    };
-    var _config = {            
-        gui: {
-            width : 620,
-            container : 'LdtPlayer',
-            default_options: {
-                metadata: _metadata
-            },
-            css : 'metadataplayer/LdtPlayer-core.css',
-            widgets: [
-                { type: "Sparkline" },
-                { type: "Slider" },
-                { type: "Controller" },
-                { type: "Polemic" },
-                { type: "Segments" },
-                { type: "Slice" },
-                { type: "Arrow" },
-                { type: "Annotation" },
-                { type: "Tweet" },
-                {
-                    type: "Tagcloud",
-                    segment_annotation_type: "chap"
-                },
-                {
-                    type: "AnnotationsList",
-                    container: "AnnotationsListContainer"
-                }
-            ]
-        },
-        player:{
-            type:'html5-audio',
-            video: 'wari_0710.mp3',
-            live: true, 
-            height: 1, 
-            width: 1,
-        }
-    };
-    _myPlayer = new IriSP.Metadataplayer(_config, _metadata);
-        </script>
-    </body>
-</html>
--- a/metadataplayer/dailymotion.htm	Fri Aug 31 17:43:31 2012 +0200
+++ b/metadataplayer/dailymotion.htm	Thu Sep 27 12:23:41 2012 +0200
@@ -15,7 +15,38 @@
         <div id="LdtPlayer"></div>
         <div id="AnnotationsListContainer"></div>
         <script type="text/javascript">
-            testConfig('json/ldt-dailymotion.json', true);
+IriSP.libFiles.defaultDir = "libs/";
+IriSP.widgetsDir = "metadataplayer";
+var _metadata = {
+    url: "json/ldt-dailymotion.json",
+    format: 'ldt'
+};
+var _config = {
+    width : 620,
+    container : 'LdtPlayer',
+    default_options: {
+        metadata: _metadata
+    },
+    css : 'metadataplayer/LdtPlayer-core.css',
+    widgets: [
+        { type: "AutoPlayer" },
+        { type: "Sparkline" },
+        { type: "Slider" },
+        { type: "Controller" },
+        { type: "Polemic" },
+        { type: "Segments" },
+        { type: "Annotation" },
+        { type: "CreateAnnotation" },
+        { type: "Tweet" },
+        { type: "Tagcloud" },
+        {
+            type: "AnnotationsList",
+            container: "AnnotationsListContainer"
+        },
+        { type: "Mediafragment"}
+    ]
+};
+_myPlayer = new IriSP.Metadataplayer(_config);
         </script>
     </body>
 </html>
--- a/metadataplayer/docs/architecture.fr.md	Fri Aug 31 17:43:31 2012 +0200
+++ b/metadataplayer/docs/architecture.fr.md	Thu Sep 27 12:23:41 2012 +0200
@@ -1,7 +1,7 @@
 # Architecture du Metadataplayer #
 
 ATTENTION !
-Cette documentation se réfère à la v.3 du Metadataplayer, actuellement disponible dans la branche **new-model** du repository
+Cette documentation se réfère à la dernière version du Metadataplayer, disponible dans la branche **default** du repository
 http://www.iri.centrepompidou.fr/dev/hg/metadataplayer
 
 ## Bibliothèques extérieures ##
@@ -62,9 +62,17 @@
 - **Fichier**: raphael-min.js
 - **Licence**: MIT.
 - **Rôle**: Fournit une interface de dessin vectoriel (utilise SVG ou VML selon les navigateurs)
-- **Utilisé par**: Widgets et Sparkline
+- **Utilisé par**: Widgets Arrow et Sparkline
 - **Site**: http://raphaeljs.com/
 
+### ZeroClipboard ###
+
+- **Fichiers**: ZeroClipboard.js et ZeroClipboard.swf
+- **Licence**: MIT.
+- **Rôle**: Permet l’accès au presse-papiers (using Flash)
+- **Utilisé par**: Widget Social
+- **Site**: http://code.google.com/p/zeroclipboard/
+
 ### ktbs4js Tracemanager ###
 
 - **Fichier**: tracemanager.js
@@ -119,6 +127,7 @@
 2. **player.dailymotion**, pour lire des vidéos du *Youtube à la française*
 3. **player.allocine**, pour le player de allocine.net
 4. **player.mashup**, pour le player de bout à bout Flash créé par Thibaut Cavalié.
+5. **player.htmlMashup**, pour le lecteur de bout à bout de vidéos HTML5 basé sur Popcorn.
 
 ### serializers ###
 
@@ -147,30 +156,27 @@
 
 Voici la liste des widgets actuellement disponibles, avec leurs options:
 
-### HelloWorld ###
+### Annotation ###
 
-- **Rôle**: Widget d’exemple démontrant l’API de création de widgets
+- **Rôle**: Affiche les informations relatives à une annotation au moment où celle-ci est jouée
 - **Options**:
-    - **text**: (défaut: "world"), texte à afficher après "Hello, "
+    - **annotation\_type**: (défaut: "chapitrage"), cf. *Options courantes*, plus haut.
+    - **show\_top\_border**: (défaut: false), afficher ou non la bordure en haut du widget (au cas où il est utilisé sans/avec le widget *Arrow*)
+    - **site\_name**: "Lignes de Temps", nom du site à afficher lorsque l’on clique sur les boutons de partage pour réseaux sociaux.
 - Utilise un fichier CSS: oui
 
-### Slider ###
+### AnnotationsList ###
 
-- **Rôle**: Barre de progression et *Slider* indiquant la position de la tête de lecture vidéo et permettant de la déplacer.
+- **Rôle**: Affiche une liste d’annotations
 - **Options**:
-   - **minimized\_height**: (défaut: 4), hauteur en pixels du *Slider* en mode minimisé
-   - **maximized\_height**: (défaut: 10), hauteur en pixels du *Slider* en mode maximisé (lorsque la souris passe dessus)
-   - **minimize\_timeout**: (défaut: 1500), durée en millisecondes avant que le *Slider* ne se minimise. À une valeur de 0, le *Slider* ne se minimise plus.
-- Utilise la bibliothèque: jQuery UI
-- Utilise un fichier CSS: oui
-
-### Controller ###
-
-- **Rôle**: Boutons Lecture/Pause, Rechercher, Ouvrir l’annotateur et contrôle du volume
-- **Options**:
-    - **disable\_annotate\_btn**: (défaut: false), permet de désactiver le bouton d’ouverture de l’annotateur s’il est à *true*
-    - **disable\_search\_btn**: (défaut: true), permet de désactiver le bouton de recherche d’annotations
-- Utilise la bibliothèque: jQuery UI
+    - **ajax\_url**: (défaut: false), spécifie un gabarit d’URL lorsque les annotations doivent être chargées par une API spécifique (API de segment). Dans l’URL, {{media}} sera remplacé par l’ID du média, {{begin}} par le *timecode* de début en millisecondes, {{end}} par le *timecode* de fin en millisecondes. Si le réglage est à *false*, les annotations affichées seront celles chargées à l’initialisation du Widget. Sur la plateforme *Lignes de Temps*, cette URL est http://ldt.iri.centrepompidou.fr/ldtplatform/api/ldt/segments/{{media}}/{{begin}}/{{end}}?callback=?
+    - **ajax\_granularity**: (défaut: 300000 ms = 5 minutes), spécifie la durée qui doit être chargée par l’API de segment, de part et d’autre du timecode courant (cf. ci-dessus) 
+    - **default\_thumbnail**: imagette à afficher par défaut à côté d’une annotation lorsque l’annotation n’a pas d’imagette.
+    - **foreign\_url**: spécifie un gabarit d’URL lorsque l’annotation n’a pas d’information d’URL et que l’annotation est dans un autre projet. Dans l’URL, {{media}} sera remplacé par l’ID du média, {{project}} par l’ID du projet, {{annotationType}} par l’ID du type d’annotation, {{annotation}} par l’ID de l’annotation. Sur la plateforme *Lignes de temps*, cette URL est http://ldt.iri.centrepompidou.fr/ldtplatform/ldt/front/player/{{media}}/{{project}}/{{annotationType}}#id={{annotation}}
+    - **annotation\_type**: (défaut: false), cf. *Options courantes*, plus haut.
+    - **refresh\_interval**: (défaut: 0), intervalle auquel le widget recharge en Ajax la liste des annotations (que l’on utilise l’API de segment ou non)
+    - **limit\_count**: (défaut: 10), nombre maximum d’annotations à afficher simultanément.
+    - **newest\_first**: (défaut: false), *true*: classe les annotations par ordre antéchronologique de création, *false*: classe les annotations par ordre chronologique de leur timecode vidéo.
 - Utilise un fichier CSS: oui
 
 ### Arrow ###
@@ -190,13 +196,13 @@
 - Utilise la bibliothèque: Raphael
 - Utilise un fichier CSS: non
 
-### Annotation ###
+### Controller ###
 
-- **Rôle**: Affiche les informations relatives à une annotation au moment où celle-ci est jouée
+- **Rôle**: Boutons Lecture/Pause, Rechercher, Ouvrir l’annotateur et contrôle du volume
 - **Options**:
-    - **annotation\_type**: (défaut: "chapitrage"), cf. *Options courantes*, plus haut.
-    - **show\_top\_border**: (défaut: false), afficher ou non la bordure en haut du widget (au cas où il est utilisé sans/avec le widget *Arrow*)
-    - **site\_name**: "Lignes de Temps", nom du site à afficher lorsque l’on clique sur les boutons de partage pour réseaux sociaux.
+    - **disable\_annotate\_btn**: (défaut: false), permet de désactiver le bouton d’ouverture de l’annotateur s’il est à *true*
+    - **disable\_search\_btn**: (défaut: true), permet de désactiver le bouton de recherche d’annotations
+- Utilise la bibliothèque: jQuery UI
 - Utilise un fichier CSS: oui
 
 ### CreateAnnotation ###
@@ -216,6 +222,28 @@
     - **close\_widget\_timeout**: (défaut: 0), durée en millisecondes avant que le widget ne soit refermé après l’envoi d’une annotation. Si la valeur est 0, le widget ne se referme pas.
 - Utilise un fichier CSS: oui
 
+### HelloWorld ###
+
+- **Rôle**: Widget d’exemple démontrant l’API de création de widgets
+- **Options**:
+    - **text**: (défaut: "world"), texte à afficher après "Hello, "
+- Utilise un fichier CSS: oui
+
+### Media ###
+
+- **Rôle**: Affiche le média en cours, ainsi que la liste des autres médias du projet. Utilisé principalement pour les mashups
+- **Options**:
+    - **default\_thumbnail**: imagette à afficher par défaut à côté d’un média lorsque le média n’a pas d’imagette.
+    - **media\_url\_template**: spécifie un gabarit d’URL lorsque le média n’a pas d’information d’URL, par exemple: "http://ldt.iri.centrepompidou.fr/ldtplatform/ldt/front/player/{{media}}/"
+- Utilise un fichier CSS: oui
+
+### Mediafragment ###
+
+- **Rôle**: Gère les URLs à la norme *Mediafragment*: change la position de la tête de lecture en fonction de l’URL et inversement.
+- Une URL finissant par #id=*id de l’annotation* pointe sur une annotation, par #t=*temps en secondes* vers un timecode de la vidéo.
+- Pas d’options
+- Utilise un fichier CSS: non.
+
 ### Polemic ###
 
 - **Rôle**: Affiche la *timeline polémique*, c’est à dire les tweets colorés en fonction de la syntaxe polémique. Selon le volume de tweets, deux modes de représentation existent:
@@ -231,12 +259,43 @@
     - **polemics**: couleurs polémiques à afficher, en fonction d’une recherche de termes, type [ { keywords: [ "++" ], color: "#1D973D" } ]
 - Utilise un fichier CSS: oui
 
-### Tweet ###
+### Renkan ###
+
+- **Rôle**: Interface avec le projet *Renkan*
+- Utilise les bibliothèques: jQuery Mousewheel, Backbone, Backbone Relational, Renkan-Publish
+- Utilise un fichier CSS: oui
+
+### Segments ###
+
+- **Rôle**: Affiche le *chapitrage* du média, en horizontal.
+- **Options**:
+    - **colors**: liste de couleurs à utiliser lorsque les annotations ne contiennent pas d’information de couleur.
+    - **height**: hauteur du widget
+- Utilise un fichier CSS: oui
+
+### Slider ###
 
-- **Rôle**: Affiche furtivement le contenu d’un tweet
+- **Rôle**: Barre de progression et *Slider* indiquant la position de la tête de lecture vidéo et permettant de la déplacer.
 - **Options**:
-    - **hide_timeout**: (défaut: 5000), durée en millisecondes, avant que l’affichage du Tweet ne se referme
-    - **polemics**: identique au paramètre *polemics* du widget *Polemic*
+   - **minimized\_height**: (défaut: 4), hauteur en pixels du *Slider* en mode minimisé
+   - **maximized\_height**: (défaut: 10), hauteur en pixels du *Slider* en mode maximisé (lorsque la souris passe dessus)
+   - **minimize\_timeout**: (défaut: 1500), durée en millisecondes avant que le *Slider* ne se minimise. À une valeur de 0, le *Slider* ne se minimise plus.
+- Utilise la bibliothèque: jQuery UI
+- Utilise un fichier CSS: oui
+
+### Social ###
+
+- **Role**: Affiche des boutons pour partager une URL sur les réseaux sociaux
+- **Options**:
+    - **text**: un texte à afficher (dans le tweet, etc.)
+    - **url**: l’URL à partager
+    - **show_url**: Affiche un bouton pour copier-coller une URL.
+    - **show_twitter**: Affiche un bouton pour partager sur Twitter.
+    - **show_fb**: Affiche un bouton pour partager sur Facebook.
+    - **show_gplus**: Affiche un bouton pour partager sur Google+.
+    - **show_mail**: Affiche un bouton pour partager par e-mail.
+- Utilise un fichier CSS: oui.
+- Utilise la bibliothèque: ZeroClipboard
 
 ### Sparkline ###
 
@@ -269,28 +328,6 @@
     - **max\_font\_size**: (défaut: 26), taille de caractères (en pixels) pour le mot le plus fréquent.
 - Utilise un fichier CSS: oui
 
-### AnnotationsList ###
-
-- **Rôle**: Affiche une liste d’annotations
-- **Options**:
-    - **ajax\_url**: (défaut: false), spécifie un gabarit d’URL lorsque les annotations doivent être chargées par une API spécifique (API de segment). Dans l’URL, {{media}} sera remplacé par l’ID du média, {{begin}} par le *timecode* de début en millisecondes, {{end}} par le *timecode* de fin en millisecondes. Si le réglage est à *false*, les annotations affichées seront celles chargées à l’initialisation du Widget. Sur la plateforme *Lignes de Temps*, cette URL est http://ldt.iri.centrepompidou.fr/ldtplatform/api/ldt/segments/{{media}}/{{begin}}/{{end}}?callback=?
-    - **ajax\_granularity**: (défaut: 300000 ms = 5 minutes), spécifie la durée qui doit être chargée par l’API de segment, de part et d’autre du timecode courant (cf. ci-dessus) 
-    - **default\_thumbnail**: imagette à afficher par défaut à côté d’une annotation lorsque l’annotation n’a pas d’imagette.
-    - **foreign\_url**: spécifie un gabarit d’URL lorsque l’annotation n’a pas d’information d’URL et que l’annotation est dans un autre projet. Dans l’URL, {{media}} sera remplacé par l’ID du média, {{project}} par l’ID du projet, {{annotationType}} par l’ID du type d’annotation, {{annotation}} par l’ID de l’annotation. Sur la plateforme *Lignes de temps*, cette URL est http://ldt.iri.centrepompidou.fr/ldtplatform/ldt/front/player/{{media}}/{{project}}/{{annotationType}}#id={{annotation}}
-    - **annotation\_type**: (défaut: false), cf. *Options courantes*, plus haut.
-    - **refresh\_interval**: (défaut: 0), intervalle auquel le widget recharge en Ajax la liste des annotations (que l’on utilise l’API de segment ou non)
-    - **limit\_count**: (défaut: 10), nombre maximum d’annotations à afficher simultanément.
-    - **newest\_first**: (défaut: false), *true*: classe les annotations par ordre antéchronologique de création, *false*: classe les annotations par ordre chronologique de leur timecode vidéo.
-- Utilise un fichier CSS: oui
-
-### Media ###
-
-- **Rôle**: Affiche le média en cours, ainsi que la liste des autres médias du projet. Utilisé principalement pour les mashups
-- **Options**:
-    - **default\_thumbnail**: imagette à afficher par défaut à côté d’un média lorsque le média n’a pas d’imagette.
-    - **media\_url\_template**: spécifie un gabarit d’URL lorsque le média n’a pas d’information d’URL, par exemple: "http://ldt.iri.centrepompidou.fr/ldtplatform/ldt/front/player/{{media}}/"
-- Utilise un fichier CSS: oui
-
 ### Tooltip ###
 
 - **Rôle**: Affiche une infobulle, utilisé uniquement comme *widget inclus* dans d’autres widgets.
@@ -306,11 +343,11 @@
     - **requestmode**: (défaut: "GET"), méthode HTTP utilisée pour l’envoi des traces (seul *"GET"* permet le *cross-domain*).
     - **syncmode**: (défaut: "sync"), envois groupés (mode *"delayed"*) ou non (*"sync"*) des traces
 - Utilise la bibliothèque: ktbs4js tracemanager
-- Utilise un fichier CSS: non
+- Utilise un fichier CSS: non.
 
-### Mediafragment ###
+### Tweet ###
 
-- **Rôle**: Gère les URLs à la norme *Mediafragment*: change la position de la tête de lecture en fonction de l’URL et inversement.
-- Une URL finissant par #id=*id de l’annotation* pointe sur une annotation, par #t=*temps en secondes* vers un timecode de la vidéo.
-- Pas d’options
-- Utilise un fichier CSS: non
+- **Rôle**: Affiche furtivement le contenu d’un tweet
+- **Options**:
+    - **hide_timeout**: (défaut: 5000), durée en millisecondes, avant que l’affichage du Tweet ne se referme
+    - **polemics**: identique au paramètre *polemics* du widget *Polemic*
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/metadataplayer/docs/data-api.fr.md	Thu Sep 27 12:23:41 2012 +0200
@@ -0,0 +1,304 @@
+# API d’accès aux métadonnées #
+
+ATTENTION !
+Cette documentation se réfère à la dernière version du Metadataplayer, disponible dans la branche **default** 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/metadataplayer/docs/data-api.md	Fri Aug 31 17:43:31 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,304 +0,0 @@
-# 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/metadataplayer/docs/integration.fr.md	Fri Aug 31 17:43:31 2012 +0200
+++ b/metadataplayer/docs/integration.fr.md	Thu Sep 27 12:23:41 2012 +0200
@@ -1,7 +1,7 @@
 # Intégration du Metadataplayer #
 
 ATTENTION !
-Cette documentation se réfère à la v.3 du Metadataplayer, actuellement disponible dans la branche **new-model** du repository
+Cette documentation se réfère à la dernière version du Metadataplayer, disponible dans la branche **default** du repository
 http://www.iri.centrepompidou.fr/dev/hg/metadataplayer
 
 ## Chargement du script ##
@@ -63,32 +63,11 @@
 
 ## Configuration de la fenêtre vidéo ##
 
-Les options de configuration de la fenêtre vidéo sont:
-
-- **metadata**: La source de métadonnées utilisée, notamment pour obtenir l’URL de la vidéo.
-- **type**: Le type de player vidéo utilisé. Les players disponibles sont :
-    - **"jwplayer"**: Utilise le player flash jwPlayer, compatible avec de nombreux formats vidéo et audio et les flux RTMP.
-    - **"html5"**: Utilise la bibliothèque Popcorn et les vidéos HTML5. Formats supportés: OGG et WebM sur Firefox et Chrome, H.264 sur Internet Explorer et Safari.
-    - **"youtube"**: Utilise le plugin Youtube de Popcorn pour afficher les vidéos Youtube.
-    - **"dailymotion"**
-    - **"auto"**: Remplacé par *Youtube* ou *Dailymotion* pour une vidéo sur l’un de ces sites et par JwPlayer sinon.
-- **width** et **height**: largeur et hauteur de la fenêtre vidéo.
-- **video**: URL de la vidéo. Si ce paramètre est présent, il écrase l’URL chargée dans les métadonnées
-- Paramètres spécifiques à chaque player, qui seront transférés tels quels, par exemple **provider** pour JwPlayer
-
-Exemple:
-
-    var playerConfig = {
-        metadata: metadataSource,
-        type: "jwplayer",
-        height: 350,
-        width: 620,
-        provider: "rtmp"
-    };
+Dans cette version, la fenêtre vidéo est désormais un widget. Cette section est donc obsolète.
 
 ## Configuration de l’interface utilisateur ##
 
-L’interface se configure par un objet GUI, contenant les propriétés suivantes:
+L’interface se configure par un objet contenant les propriétés suivantes:
 
 - **container**: l’ID de l’élément HTML dans lequel le player sera instancié.
 - **width** et **height**: largeur et hauteur de l’interface (*height* est optionnel).
@@ -98,7 +77,7 @@
 
 Exemple:
 
-    var guiConfig = {
+    var config = {
         container : "Metadataplayer",
         default_options: {
             metadata: metadataSource
@@ -106,6 +85,9 @@
         css : "metadataplayer/css/LdtPlayer-core.css",
         widgets: [
             {
+                type: "AutoPlayer"
+            },
+            {
                 type: "Slider"
             },{
                 type: "Controller",
@@ -122,12 +104,8 @@
 
 ## Instanciation du player ##
 
-Le player s’instancie en créant un objet de type **IriSP.Metadataplayer**.
+Le player s’instancie en créant un objet de classe **IriSP.Metadataplayer**.
 
 Exemple:
 
-    var config = {
-        player: playerConfig,
-        gui: guiConfig
-    };
     var monPlayer = new IriSP.Metadataplayer(config);
--- a/metadataplayer/docs/widget-api.fr.md	Fri Aug 31 17:43:31 2012 +0200
+++ b/metadataplayer/docs/widget-api.fr.md	Thu Sep 27 12:23:41 2012 +0200
@@ -1,7 +1,7 @@
 # API de programmation de widgets #
 
 ATTENTION !
-Cette documentation se réfère à la v.3 du Metadataplayer, actuellement disponible dans la branche **new-model** du repository
+Cette documentation se réfère à la dernière version du Metadataplayer, disponible dans la branche **default** du repository
 http://www.iri.centrepompidou.fr/dev/hg/metadataplayer
 
 ## Types et fichiers ##
@@ -68,15 +68,15 @@
 
 ### Propriétés accessibles par le widget ###
 
-#### Popcorn ####
+#### Media ####
 
-Les fonctions de gestion de la lecture vidéo sont accessibles par la propriété **popcorn** du Metadataplayer.
+Les fonctions de gestion de la lecture vidéo sont accessibles via les objets **media**.
 
 Par exemple:
 
-    this.player.popcorn.trigger("Evenement"); => déclenche un événement Popcorn de type "Evenement"
-    this.player.popcorn.play();               => met le player en lecture
-    this.player.popcorn.currentTime()         => obtient le timecode courant, en secondes
+    this.media.trigger("Evenement"); => déclenche un événement Popcorn de type "Evenement"
+    this.media.play();               => met le player en lecture
+    this.media.currentTime()         => obtient le timecode courant, en secondes
 
 #### Source de métadonnées ####
 
@@ -119,13 +119,20 @@
 
     this.$.click(this.functionWrapper("onClick"));  => Appellera this.onClick() lors d’un click sur le widget
 
-#### bindPopcorn ####
+#### onMediaEvent ####
 
-Attache un événement *Popcorn* à une fonction, sur le même mode que *functionWrapper*
+Attache un événement *Media* ("play", "timeupdate", ...) à une fonction désignée
+- par son nom, quand celle-ci est une méthode du widget, ce qui permet de l’associer directement au widget, sur le même mode que *functionWrapper*
+- comme fonction anonyme
 
 Par exemple:
 
-    this.bindPopcorn("timeupdate","onTimeupdate");  => Appellera this.onTimeupdate() lorsque l’événement Popcorn "timeupdate" est déclenché.
+    this.onMediaEvent("timeupdate","onTimeupdate");                => Appellera this.onTimeupdate() lorsque l’événement media "timeupdate" est déclenché.
+    this.onMediaEvent("play", function() { console.log("play") }); => Affichera "play" dans la console JavaScript
+
+#### onMdpEvent ####
+
+Attache un événement Metadataplayer global ("Annotation.show", "search.found", ...) à une fonction. Utilisé de manière similaire à onMediaEvent.
 
 #### Autres fonctions ####
 
--- a/metadataplayer/index.htm	Fri Aug 31 17:43:31 2012 +0200
+++ b/metadataplayer/index.htm	Thu Sep 27 12:23:41 2012 +0200
@@ -12,8 +12,10 @@
         <h1>Metadataplayer test configurations</h1>
         <ul class="pageindex">
             <li><h2><a href="jwplayer.htm">with JwPlayer (default on Ldt Platform, uses custom player functions)</a></h2></li>
-            <li><h2><a href="oggvideo.htm">with HTML5/OGG (uses Popcorn.js)</a></h2></li>
+            <li><h2><a href="oggvideo.htm">with HTML5/OGG (uses Popcorn.js, compatible with Firefox and Chrome)</a></h2></li>
+            <li><h2><a href="mp4video.htm">with HTML5/MP4 (uses Popcorn.js, compatible with IE, Safari and Chrome)</a></h2></li>
             <li><h2><a href="youtube.htm">with Youtube (uses Popcorn.js and the Popcorn Youtube plugin/player)</a></h2></li>
+            <li><h2><a href="vimeo.htm">with Vimeo (uses Popcorn.js and the Popcorn Vimeo plugin/player)</a></h2></li>
             <li><h2><a href="dailymotion.htm">with Dailymotion (uses custom player functions)</a></h2></li>
         </ul>
     </body>
--- a/metadataplayer/jwplayer.htm	Fri Aug 31 17:43:31 2012 +0200
+++ b/metadataplayer/jwplayer.htm	Thu Sep 27 12:23:41 2012 +0200
@@ -15,58 +15,69 @@
         <div id="LdtPlayer"></div>
         <div id="AnnotationsListContainer"></div>
         <script type="text/javascript">
-    IriSP.libFiles.locations.jwPlayerSWF = "player.swf";
     IriSP.libFiles.defaultDir = "libs/";
+    IriSP.language = 'fr';
     IriSP.widgetsDir = "metadataplayer";
     var _metadata = {
         url: 'json/ldt-jwplayer.json',
+//        url: 'http://ldt.iri.centrepompidou.fr/ldtplatform/ldt/cljson/id/5a893570-ca73-11e1-9443-00145ea4a2be?callback=?',
         format: 'ldt'
     };
-    var _config = {            
-        gui: {
-            width : 620,
-            container : 'LdtPlayer',
-            default_options: {
-                metadata: _metadata
+    var _config = {
+        width : 550,
+        container : 'LdtPlayer',
+        default_options: {
+            metadata: _metadata
+        },
+        css : 'metadataplayer/LdtPlayer-core.css',
+        widgets: [
+            {
+                type: "AutoPlayer",
+                live: true,
+                width: 550, 
+                provider: "rtmp",
+                autostart: true,
+                streamer: function(_url) {
+                    var _matches = _url.match(/^[^\/]+\/\/[^\/]+\/[^\/]+\//);
+                    if (_matches) {
+                        return _matches[0];
+                    } else {
+                        return _url;
+                    }
+                }
             },
-            css : 'metadataplayer/LdtPlayer-core.css',
-            widgets: [
-                { type: "Sparkline" },
-                { type: "Slider" },
-                { type: "Controller" },
-                { type: "Polemic" },
-                { type: "Segments" },
-                { type: "Slice" },
-                { type: "Arrow" },
-                { type: "Annotation" },
-                {
-                    type: "Tagger",
-                    api_endpoint: "post-test.php",
-                },
-                { type: "Tweet" },
-                {
-                    type: "Tagcloud",
-                    segment_annotation_type: "chap"
-                },
-                {
-                    type: "AnnotationsList",
-                    container: "AnnotationsListContainer"
-                },
-                { type: "Mediafragment"}
-            ]
-        },
-        player:{
-            type:'auto',
-            live: true, 
-            height: 350, 
-            width: 620, 
-            provider: "rtmp",
-            autostart: true,
-            metadata: _metadata
-        }
+            { type: "Slider" },
+            { type: "Controller" },
+            { type: "Polemic" },
+            { type: "Segments" },
+            { type: "Annotation" },
+            {
+                type: "CreateAnnotation",
+                api_endpoint_template: "post-test.php",
+                creator_name: "Metadataplayer",
+                creator_avatar: "https://si0.twimg.com/sticky/default_profile_images/default_profile_1_normal.png",
+                tag_titles: ["#amateur", "#digital-humanities"]
+            },
+            { type: "Tweet" },
+            {
+                type: "Tagger",
+                api_endpoint: "post-test.php"
+            },
+            {
+                type: "Tagcloud"
+            },
+            {
+                type: "AnnotationsList",
+                container: "AnnotationsListContainer",
+                default_thumbnail : "http://ldt.iri.centrepompidou.fr/static/site/ldt/css/imgs/video_sequence.png",
+                //ajax_url: "/pf/ldtplatform/api/ldt/segments/{{media}}/{{begin}}/{{end}}",
+                //ajax_granularity : 300000
+            },
+            { type: "Mediafragment"}
+        ]
     };
     
-    _myPlayer = new IriSP.Metadataplayer(_config);
+    var _myPlayer = new IriSP.Metadataplayer(_config);
     
         </script>
     </body>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/metadataplayer/libs/backbone-relational.js	Thu Sep 27 12:23:41 2012 +0200
@@ -0,0 +1,1687 @@
+/**
+ * Backbone-relational.js 0.6.0
+ * (c) 2011 Paul Uithol
+ * 
+ * Backbone-relational may be freely distributed under the MIT license; see the accompanying LICENSE.txt.
+ * For details and documentation: https://github.com/PaulUithol/Backbone-relational.
+ * Depends on Backbone (and thus on Underscore as well): https://github.com/documentcloud/backbone.
+ */
+( function( undefined ) {
+	"use strict";
+	
+	/**
+	 * CommonJS shim
+	 **/
+	var _, Backbone, exports;
+	if ( typeof window === 'undefined' ) {
+		_ = require( 'underscore' );
+		Backbone = require( 'backbone' );
+		exports = module.exports = Backbone;
+	}
+	else {
+		_ = window._;
+		Backbone = window.Backbone;
+		exports = window;
+	}
+
+	Backbone.Relational = {
+		showWarnings: true
+	};
+
+	/**
+	 * Semaphore mixin; can be used as both binary and counting.
+	 **/
+	Backbone.Semaphore = {
+		_permitsAvailable: null,
+		_permitsUsed: 0,
+		
+		acquire: function() {
+			if ( this._permitsAvailable && this._permitsUsed >= this._permitsAvailable ) {
+				throw new Error( 'Max permits acquired' );
+			}
+			else {
+				this._permitsUsed++;
+			}
+		},
+		
+		release: function() {
+			if ( this._permitsUsed === 0 ) {
+				throw new Error( 'All permits released' );
+			}
+			else {
+				this._permitsUsed--;
+			}
+		},
+		
+		isLocked: function() {
+			return this._permitsUsed > 0;
+		},
+		
+		setAvailablePermits: function( amount ) {
+			if ( this._permitsUsed > amount ) {
+				throw new Error( 'Available permits cannot be less than used permits' );
+			}
+			this._permitsAvailable = amount;
+		}
+	};
+	
+	/**
+	 * A BlockingQueue that accumulates items while blocked (via 'block'),
+	 * and processes them when unblocked (via 'unblock').
+	 * Process can also be called manually (via 'process').
+	 */
+	Backbone.BlockingQueue = function() {
+		this._queue = [];
+	};
+	_.extend( Backbone.BlockingQueue.prototype, Backbone.Semaphore, {
+		_queue: null,
+		
+		add: function( func ) {
+			if ( this.isBlocked() ) {
+				this._queue.push( func );
+			}
+			else {
+				func();
+			}
+		},
+		
+		process: function() {
+			while ( this._queue && this._queue.length ) {
+				this._queue.shift()();
+			}
+		},
+		
+		block: function() {
+			this.acquire();
+		},
+		
+		unblock: function() {
+			this.release();
+			if ( !this.isBlocked() ) {
+				this.process();
+			}
+		},
+		
+		isBlocked: function() {
+			return this.isLocked();
+		}
+	});
+	/**
+	 * Global event queue. Accumulates external events ('add:<key>', 'remove:<key>' and 'update:<key>')
+	 * until the top-level object is fully initialized (see 'Backbone.RelationalModel').
+	 */
+	Backbone.Relational.eventQueue = new Backbone.BlockingQueue();
+	
+	/**
+	 * Backbone.Store keeps track of all created (and destruction of) Backbone.RelationalModel.
+	 * Handles lookup for relations.
+	 */
+	Backbone.Store = function() {
+		this._collections = [];
+		this._reverseRelations = [];
+		this._subModels = [];
+		this._modelScopes = [ exports ];
+	};
+	_.extend( Backbone.Store.prototype, Backbone.Events, {
+		addModelScope: function( scope ) {
+			this._modelScopes.push( scope );
+		},
+
+		/**
+		 * Add a set of subModelTypes to the store, that can be used to resolve the '_superModel'
+		 * for a model later in 'setupSuperModel'.
+		 *
+		 * @param {Backbone.RelationalModel} subModelTypes
+		 * @param {Backbone.RelationalModel} superModelType
+		 */
+		addSubModels: function( subModelTypes, superModelType ) {
+			this._subModels.push({
+				'superModelType': superModelType,
+				'subModels': subModelTypes
+			});
+		},
+
+		/**
+		 * Check if the given modelType is registered as another model's subModel. If so, add it to the super model's
+		 * '_subModels', and set the modelType's '_superModel', '_subModelTypeName', and '_subModelTypeAttribute'.
+		 *
+		 * @param {Backbone.RelationalModel} modelType
+		 */
+		setupSuperModel: function( modelType ) {
+			_.find( this._subModels, function( subModelDef ) {
+				return _.find( subModelDef.subModels, function( subModelTypeName, typeValue ) {
+					var subModelType = this.getObjectByName( subModelTypeName );
+
+					if ( modelType === subModelType ) {
+						// Set 'modelType' as a child of the found superModel
+						subModelDef.superModelType._subModels[ typeValue ] = modelType;
+
+						// Set '_superModel', '_subModelTypeValue', and '_subModelTypeAttribute' on 'modelType'.
+						modelType._superModel = subModelDef.superModelType;
+						modelType._subModelTypeValue = typeValue;
+						modelType._subModelTypeAttribute = subModelDef.superModelType.prototype.subModelTypeAttribute;
+						return true;
+					}
+				}, this );
+			}, this );
+		},
+		
+		/**
+		 * Add a reverse relation. Is added to the 'relations' property on model's prototype, and to
+		 * existing instances of 'model' in the store as well.
+		 * @param {Object} relation
+		 * @param {Backbone.RelationalModel} relation.model
+		 * @param {String} relation.type
+		 * @param {String} relation.key
+		 * @param {String|Object} relation.relatedModel
+		 */
+		addReverseRelation: function( relation ) {
+			var exists = _.any( this._reverseRelations, function( rel ) {
+					return _.all( relation, function( val, key ) {
+							return val === rel[ key ];
+						});
+				});
+			
+			if ( !exists && relation.model && relation.type ) {
+				this._reverseRelations.push( relation );
+				
+				var addRelation = function( model, relation ) {
+					if ( !model.prototype.relations ) {
+						model.prototype.relations = [];
+					}
+					model.prototype.relations.push( relation );
+					
+					_.each( model._subModels, function( subModel ) {
+							addRelation( subModel, relation );
+						}, this );
+				};
+				
+				addRelation( relation.model, relation );
+				
+				this.retroFitRelation( relation );
+			}
+		},
+		
+		/**
+		 * Add a 'relation' to all existing instances of 'relation.model' in the store
+		 * @param {Object} relation
+		 */
+		retroFitRelation: function( relation ) {
+			var coll = this.getCollection( relation.model );
+			coll.each( function( model ) {
+				if ( !( model instanceof relation.model ) ) {
+					return;
+				}
+
+				new relation.type( model, relation );
+			}, this);
+		},
+		
+		/**
+		 * Find the Store's collection for a certain type of model.
+		 * @param {Backbone.RelationalModel} model
+		 * @return {Backbone.Collection} A collection if found (or applicable for 'model'), or null
+		 */
+		getCollection: function( model ) {
+			if ( model instanceof Backbone.RelationalModel ) {
+				model = model.constructor;
+			}
+			
+			var rootModel = model;
+			while ( rootModel._superModel ) {
+				rootModel = rootModel._superModel;
+			}
+			
+			var coll = _.detect( this._collections, function( c ) {
+					return c.model === rootModel;
+				});
+			
+			if ( !coll ) {
+				coll = this._createCollection( rootModel );
+			}
+			
+			return coll;
+		},
+		
+		/**
+		 * Find a type on the global object by name. Splits name on dots.
+		 * @param {String} name
+		 * @return {Object}
+		 */
+		getObjectByName: function( name ) {
+			var parts = name.split( '.' ),
+				type = null;
+
+			_.find( this._modelScopes, function( scope ) {
+				type = _.reduce( parts, function( memo, val ) {
+					return memo[ val ];
+				}, scope );
+
+				if ( type && type !== scope ) {
+					return true;
+				}
+			}, this );
+
+			return type;
+		},
+		
+		_createCollection: function( type ) {
+			var coll;
+			
+			// If 'type' is an instance, take its constructor
+			if ( type instanceof Backbone.RelationalModel ) {
+				type = type.constructor;
+			}
+			
+			// Type should inherit from Backbone.RelationalModel.
+			if ( type.prototype instanceof Backbone.RelationalModel ) {
+				coll = new Backbone.Collection();
+				coll.model = type;
+				
+				this._collections.push( coll );
+			}
+			
+			return coll;
+		},
+
+		/**
+		 * Find the attribute that is to be used as the `id` on a given object
+		 * @param type
+		 * @param {String|Number|Object|Backbone.RelationalModel} item
+		 * @return {String|Number}
+		 */
+		resolveIdForItem: function( type, item ) {
+			var id = _.isString( item ) || _.isNumber( item ) ? item : null;
+
+			if ( id === null ) {
+				if ( item instanceof Backbone.RelationalModel ) {
+					id = item.id;
+				}
+				else if ( _.isObject( item ) ) {
+					id = item[ type.prototype.idAttribute ];
+				}
+			}
+
+			// Make all falsy values `null` (except for 0, which could be an id.. see '/issues/179')
+			if ( !id && id !== 0 ) {
+				id = null;
+			}
+
+			return id;
+		},
+
+		/**
+		 *
+		 * @param type
+		 * @param {String|Number|Object|Backbone.RelationalModel} item
+		 */
+		find: function( type, item ) {
+			var id = this.resolveIdForItem( type, item );
+			var coll = this.getCollection( type );
+			
+			// Because the found object could be of any of the type's superModel
+			// types, only return it if it's actually of the type asked for.
+			if ( coll ) {
+				var obj = coll.get( id );
+
+				if ( obj instanceof type ) {
+					return obj;
+				}
+			}
+
+			return null;
+		},
+		
+		/**
+		 * Add a 'model' to it's appropriate collection. Retain the original contents of 'model.collection'.
+		 * @param {Backbone.RelationalModel} model
+		 */
+		register: function( model ) {
+			var coll = this.getCollection( model );
+
+			if ( coll ) {
+				if ( coll.get( model ) ) {
+					throw new Error( "Cannot instantiate more than one Backbone.RelationalModel with the same id per type!" );
+				}
+
+				var modelColl = model.collection;
+				coll.add( model );
+				model.bind( 'destroy', this.unregister, this );
+				model.collection = modelColl;
+			}
+		},
+		
+		/**
+		 * Explicitly update a model's id in it's store collection
+		 * @param {Backbone.RelationalModel} model
+		 */
+		update: function( model ) {
+			var coll = this.getCollection( model );
+			coll._onModelEvent( 'change:' + model.idAttribute, model, coll );
+		},
+		
+		/**
+		 * Remove a 'model' from the store.
+		 * @param {Backbone.RelationalModel} model
+		 */
+		unregister: function( model ) {
+			model.unbind( 'destroy', this.unregister );
+			var coll = this.getCollection( model );
+			coll && coll.remove( model );
+		}
+	});
+	Backbone.Relational.store = new Backbone.Store();
+	
+	/**
+	 * The main Relation class, from which 'HasOne' and 'HasMany' inherit. Internally, 'relational:<key>' events
+	 * are used to regulate addition and removal of models from relations.
+	 *
+	 * @param {Backbone.RelationalModel} instance
+	 * @param {Object} options
+	 * @param {string} options.key
+	 * @param {Backbone.RelationalModel.constructor} options.relatedModel
+	 * @param {Boolean|String} [options.includeInJSON=true] Serialize the given attribute for related model(s)' in toJSON, or just their ids.
+	 * @param {Boolean} [options.createModels=true] Create objects from the contents of keys if the object is not found in Backbone.store.
+	 * @param {Object} [options.reverseRelation] Specify a bi-directional relation. If provided, Relation will reciprocate
+	 *    the relation to the 'relatedModel'. Required and optional properties match 'options', except that it also needs
+	 *    {Backbone.Relation|String} type ('HasOne' or 'HasMany').
+	 */
+	Backbone.Relation = function( instance, options ) {
+		this.instance = instance;
+		// Make sure 'options' is sane, and fill with defaults from subclasses and this object's prototype
+		options = _.isObject( options ) ? options : {};
+		this.reverseRelation = _.defaults( options.reverseRelation || {}, this.options.reverseRelation );
+		this.reverseRelation.type = !_.isString( this.reverseRelation.type ) ? this.reverseRelation.type :
+			Backbone[ this.reverseRelation.type ] || Backbone.Relational.store.getObjectByName( this.reverseRelation.type );
+		this.model = options.model || this.instance.constructor;
+		this.options = _.defaults( options, this.options, Backbone.Relation.prototype.options );
+		
+		this.key = this.options.key;
+		this.keySource = this.options.keySource || this.key;
+		this.keyDestination = this.options.keyDestination || this.keySource || this.key;
+
+		// 'exports' should be the global object where 'relatedModel' can be found on if given as a string.
+		this.relatedModel = this.options.relatedModel;
+		if ( _.isString( this.relatedModel ) ) {
+			this.relatedModel = Backbone.Relational.store.getObjectByName( this.relatedModel );
+		}
+
+		if ( !this.checkPreconditions() ) {
+			return false;
+		}
+
+		if ( instance ) {
+			this.keyContents = this.instance.get( this.keySource );
+
+			// Explicitly clear 'keySource', to prevent a leaky abstraction if 'keySource' differs from 'key'.
+			if ( this.key !== this.keySource ) {
+				this.instance.unset( this.keySource, { silent: true } );
+			}
+
+			// Add this Relation to instance._relations
+			this.instance._relations.push( this );
+		}
+
+		// Add the reverse relation on 'relatedModel' to the store's reverseRelations
+		if ( !this.options.isAutoRelation && this.reverseRelation.type && this.reverseRelation.key ) {
+			Backbone.Relational.store.addReverseRelation( _.defaults( {
+					isAutoRelation: true,
+					model: this.relatedModel,
+					relatedModel: this.model,
+					reverseRelation: this.options // current relation is the 'reverseRelation' for it's own reverseRelation
+				},
+				this.reverseRelation // Take further properties from this.reverseRelation (type, key, etc.)
+			) );
+		}
+
+		_.bindAll( this, '_modelRemovedFromCollection', '_relatedModelAdded', '_relatedModelRemoved' );
+
+		if ( instance ) {
+			this.initialize();
+
+			// When a model in the store is destroyed, check if it is 'this.instance'.
+			Backbone.Relational.store.getCollection( this.instance )
+				.bind( 'relational:remove', this._modelRemovedFromCollection );
+
+			// When 'relatedModel' are created or destroyed, check if it affects this relation.
+			Backbone.Relational.store.getCollection( this.relatedModel )
+				.bind( 'relational:add', this._relatedModelAdded )
+				.bind( 'relational:remove', this._relatedModelRemoved );
+		}
+	};
+	// Fix inheritance :\
+	Backbone.Relation.extend = Backbone.Model.extend;
+	// Set up all inheritable **Backbone.Relation** properties and methods.
+	_.extend( Backbone.Relation.prototype, Backbone.Events, Backbone.Semaphore, {
+		options: {
+			createModels: true,
+			includeInJSON: true,
+			isAutoRelation: false
+		},
+		
+		instance: null,
+		key: null,
+		keyContents: null,
+		relatedModel: null,
+		reverseRelation: null,
+		related: null,
+		
+		_relatedModelAdded: function( model, coll, options ) {
+			// Allow 'model' to set up it's relations, before calling 'tryAddRelated'
+			// (which can result in a call to 'addRelated' on a relation of 'model')
+			var dit = this;
+			model.queue( function() {
+				dit.tryAddRelated( model, options );
+			});
+		},
+		
+		_relatedModelRemoved: function( model, coll, options ) {
+			this.removeRelated( model, options );
+		},
+		
+		_modelRemovedFromCollection: function( model ) {
+			if ( model === this.instance ) {
+				this.destroy();
+			}
+		},
+		
+		/**
+		 * Check several pre-conditions.
+		 * @return {Boolean} True if pre-conditions are satisfied, false if they're not.
+		 */
+		checkPreconditions: function() {
+			var i = this.instance,
+				k = this.key,
+				m = this.model,
+				rm = this.relatedModel,
+				warn = Backbone.Relational.showWarnings && typeof console !== 'undefined';
+
+			if ( !m || !k || !rm ) {
+				warn && console.warn( 'Relation=%o; no model, key or relatedModel (%o, %o, %o)', this, m, k, rm );
+				return false;
+			}
+			// Check if the type in 'model' inherits from Backbone.RelationalModel
+			if ( !( m.prototype instanceof Backbone.RelationalModel ) ) {
+				warn && console.warn( 'Relation=%o; model does not inherit from Backbone.RelationalModel (%o)', this, i );
+				return false;
+			}
+			// Check if the type in 'relatedModel' inherits from Backbone.RelationalModel
+			if ( !( rm.prototype instanceof Backbone.RelationalModel ) ) {
+				warn && console.warn( 'Relation=%o; relatedModel does not inherit from Backbone.RelationalModel (%o)', this, rm );
+				return false;
+			}
+			// Check if this is not a HasMany, and the reverse relation is HasMany as well
+			if ( this instanceof Backbone.HasMany && this.reverseRelation.type === Backbone.HasMany ) {
+				warn && console.warn( 'Relation=%o; relation is a HasMany, and the reverseRelation is HasMany as well.', this );
+				return false;
+			}
+
+			// Check if we're not attempting to create a duplicate relationship
+			if ( i && i._relations.length ) {
+				var exists = _.any( i._relations, function( rel ) {
+						var hasReverseRelation = this.reverseRelation.key && rel.reverseRelation.key;
+						return rel.relatedModel === rm && rel.key === k &&
+							( !hasReverseRelation || this.reverseRelation.key === rel.reverseRelation.key );
+					}, this );
+
+				if ( exists ) {
+					warn && console.warn( 'Relation=%o between instance=%o.%s and relatedModel=%o.%s already exists',
+						this, i, k, rm, this.reverseRelation.key );
+					return false;
+				}
+			}
+
+			return true;
+		},
+
+		/**
+		 * Set the related model(s) for this relation
+		 * @param {Backbone.Mode|Backbone.Collection} related
+		 * @param {Object} [options]
+		 */
+		setRelated: function( related, options ) {
+			this.related = related;
+
+			this.instance.acquire();
+			this.instance.set( this.key, related, _.defaults( options || {}, { silent: true } ) );
+			this.instance.release();
+		},
+		
+		/**
+		 * Determine if a relation (on a different RelationalModel) is the reverse
+		 * relation of the current one.
+		 * @param {Backbone.Relation} relation
+		 * @return {Boolean}
+		 */
+		_isReverseRelation: function( relation ) {
+			if ( relation.instance instanceof this.relatedModel && this.reverseRelation.key === relation.key &&
+					this.key === relation.reverseRelation.key ) {
+				return true;
+			}
+			return false;
+		},
+		
+		/**
+		 * Get the reverse relations (pointing back to 'this.key' on 'this.instance') for the currently related model(s).
+		 * @param {Backbone.RelationalModel} [model] Get the reverse relations for a specific model.
+		 *    If not specified, 'this.related' is used.
+		 * @return {Backbone.Relation[]}
+		 */
+		getReverseRelations: function( model ) {
+			var reverseRelations = [];
+			// Iterate over 'model', 'this.related.models' (if this.related is a Backbone.Collection), or wrap 'this.related' in an array.
+			var models = !_.isUndefined( model ) ? [ model ] : this.related && ( this.related.models || [ this.related ] );
+			_.each( models , function( related ) {
+					_.each( related.getRelations(), function( relation ) {
+							if ( this._isReverseRelation( relation ) ) {
+								reverseRelations.push( relation );
+							}
+						}, this );
+				}, this );
+			
+			return reverseRelations;
+		},
+		
+		/**
+		 * Rename options.silent to options.silentChange, so events propagate properly.
+		 * (for example in HasMany, from 'addRelated'->'handleAddition')
+		 * @param {Object} [options]
+		 * @return {Object}
+		 */
+		sanitizeOptions: function( options ) {
+			options = options ? _.clone( options ) : {};
+			if ( options.silent ) {
+				options.silentChange = true;
+				delete options.silent;
+			}
+			return options;
+		},
+
+		/**
+		 * Rename options.silentChange to options.silent, so events are silenced as intended in Backbone's
+		 * original functions.
+		 * @param {Object} [options]
+		 * @return {Object}
+		 */
+		unsanitizeOptions: function( options ) {
+			options = options ? _.clone( options ) : {};
+			if ( options.silentChange ) {
+				options.silent = true;
+				delete options.silentChange;
+			}
+			return options;
+		},
+		
+		// Cleanup. Get reverse relation, call removeRelated on each.
+		destroy: function() {
+			Backbone.Relational.store.getCollection( this.instance )
+				.unbind( 'relational:remove', this._modelRemovedFromCollection );
+			
+			Backbone.Relational.store.getCollection( this.relatedModel )
+				.unbind( 'relational:add', this._relatedModelAdded )
+				.unbind( 'relational:remove', this._relatedModelRemoved );
+			
+			_.each( this.getReverseRelations(), function( relation ) {
+					relation.removeRelated( this.instance );
+				}, this );
+		}
+	});
+	
+	Backbone.HasOne = Backbone.Relation.extend({
+		options: {
+			reverseRelation: { type: 'HasMany' }
+		},
+		
+		initialize: function() {
+			_.bindAll( this, 'onChange' );
+
+			this.instance.bind( 'relational:change:' + this.key, this.onChange );
+
+			var model = this.findRelated( { silent: true } );
+			this.setRelated( model );
+
+			// Notify new 'related' object of the new relation.
+			_.each( this.getReverseRelations(), function( relation ) {
+					relation.addRelated( this.instance );
+				}, this );
+		},
+		
+		findRelated: function( options ) {
+			var item = this.keyContents;
+			var model = null;
+			
+			if ( item instanceof this.relatedModel ) {
+				model = item;
+			}
+			else if ( item || item === 0 ) { // since 0 can be a valid `id` as well
+				model = this.relatedModel.findOrCreate( item, { create: this.options.createModels } );
+			}
+			
+			return model;
+		},
+		
+		/**
+		 * If the key is changed, notify old & new reverse relations and initialize the new relation
+		 */
+		onChange: function( model, attr, options ) {
+			// Don't accept recursive calls to onChange (like onChange->findRelated->findOrCreate->initializeRelations->addRelated->onChange)
+			if ( this.isLocked() ) {
+				return;
+			}
+			this.acquire();
+			options = this.sanitizeOptions( options );
+			
+			// 'options._related' is set by 'addRelated'/'removeRelated'. If it is set, the change
+			// is the result of a call from a relation. If it's not, the change is the result of 
+			// a 'set' call on this.instance.
+			var changed = _.isUndefined( options._related );
+			var oldRelated = changed ? this.related : options._related;
+			
+			if ( changed ) {	
+				this.keyContents = attr;
+				
+				// Set new 'related'
+				if ( attr instanceof this.relatedModel ) {
+					this.related = attr;
+				}
+				else if ( attr ) {
+					var related = this.findRelated( options );
+					this.setRelated( related );
+				}
+				else {
+					this.setRelated( null );
+				}
+			}
+			
+			// Notify old 'related' object of the terminated relation
+			if ( oldRelated && this.related !== oldRelated ) {
+				_.each( this.getReverseRelations( oldRelated ), function( relation ) {
+						relation.removeRelated( this.instance, options );
+					}, this );
+			}
+			
+			// Notify new 'related' object of the new relation. Note we do re-apply even if this.related is oldRelated;
+			// that can be necessary for bi-directional relations if 'this.instance' was created after 'this.related'.
+			// In that case, 'this.instance' will already know 'this.related', but the reverse might not exist yet.
+			_.each( this.getReverseRelations(), function( relation ) {
+					relation.addRelated( this.instance, options );
+				}, this);
+			
+			// Fire the 'update:<key>' event if 'related' was updated
+			if ( !options.silentChange && this.related !== oldRelated ) {
+				var dit = this;
+				Backbone.Relational.eventQueue.add( function() {
+					dit.instance.trigger( 'update:' + dit.key, dit.instance, dit.related, options );
+				});
+			}
+			this.release();
+		},
+		
+		/**
+		 * If a new 'this.relatedModel' appears in the 'store', try to match it to the last set 'keyContents'
+		 */
+		tryAddRelated: function( model, options ) {
+			if ( this.related ) {
+				return;
+			}
+			options = this.sanitizeOptions( options );
+			
+			var item = this.keyContents;
+			if ( item || item === 0 ) { // since 0 can be a valid `id` as well
+				var id = Backbone.Relational.store.resolveIdForItem( this.relatedModel, item );
+				if ( !_.isNull( id ) && model.id === id ) {
+					this.addRelated( model, options );
+				}
+			}
+		},
+		
+		addRelated: function( model, options ) {
+			if ( model !== this.related ) {
+				var oldRelated = this.related || null;
+				this.setRelated( model );
+				this.onChange( this.instance, model, { _related: oldRelated } );
+			}
+		},
+		
+		removeRelated: function( model, options ) {
+			if ( !this.related ) {
+				return;
+			}
+			
+			if ( model === this.related ) {
+				var oldRelated = this.related || null;
+				this.setRelated( null );
+				this.onChange( this.instance, model, { _related: oldRelated } );
+			}
+		}
+	});
+	
+	Backbone.HasMany = Backbone.Relation.extend({
+		collectionType: null,
+		
+		options: {
+			reverseRelation: { type: 'HasOne' },
+			collectionType: Backbone.Collection,
+			collectionKey: true,
+			collectionOptions: {}
+		},
+		
+		initialize: function() {
+			_.bindAll( this, 'onChange', 'handleAddition', 'handleRemoval', 'handleReset' );
+			this.instance.bind( 'relational:change:' + this.key, this.onChange );
+			
+			// Handle a custom 'collectionType'
+			this.collectionType = this.options.collectionType;
+			if ( _.isString( this.collectionType ) ) {
+				this.collectionType = Backbone.Relational.store.getObjectByName( this.collectionType );
+			}
+			if ( !this.collectionType.prototype instanceof Backbone.Collection ){
+				throw new Error( 'collectionType must inherit from Backbone.Collection' );
+			}
+
+			// Handle cases where a model/relation is created with a collection passed straight into 'attributes'
+			if ( this.keyContents instanceof Backbone.Collection ) {
+				this.setRelated( this._prepareCollection( this.keyContents ) );
+			}
+			else {
+				this.setRelated( this._prepareCollection() );
+			}
+
+			this.findRelated( { silent: true } );
+		},
+		
+		_getCollectionOptions: function() {
+			return _.isFunction( this.options.collectionOptions ) ?
+				this.options.collectionOptions( this.instance ) :
+				this.options.collectionOptions;
+		},
+
+		/**
+		 * Bind events and setup collectionKeys for a collection that is to be used as the backing store for a HasMany.
+		 * If no 'collection' is supplied, a new collection will be created of the specified 'collectionType' option.
+		 * @param {Backbone.Collection} [collection]
+		 */
+		_prepareCollection: function( collection ) {
+			if ( this.related ) {
+				this.related
+					.unbind( 'relational:add', this.handleAddition )
+					.unbind( 'relational:remove', this.handleRemoval )
+					.unbind( 'relational:reset', this.handleReset )
+			}
+
+			if ( !collection || !( collection instanceof Backbone.Collection ) ) {
+				collection = new this.collectionType( [], this._getCollectionOptions() );
+			}
+
+			collection.model = this.relatedModel;
+			
+			if ( this.options.collectionKey ) {
+				var key = this.options.collectionKey === true ? this.options.reverseRelation.key : this.options.collectionKey;
+				
+				if ( collection[ key ] && collection[ key ] !== this.instance ) {
+					if ( Backbone.Relational.showWarnings && typeof console !== 'undefined' ) {
+						console.warn( 'Relation=%o; collectionKey=%s already exists on collection=%o', this, key, this.options.collectionKey );
+					}
+				}
+				else if ( key ) {
+					collection[ key ] = this.instance;
+				}
+			}
+			
+			collection
+				.bind( 'relational:add', this.handleAddition )
+				.bind( 'relational:remove', this.handleRemoval )
+				.bind( 'relational:reset', this.handleReset );
+			
+			return collection;
+		},
+		
+		findRelated: function( options ) {
+			if ( this.keyContents ) {
+				var models = [];
+
+				if ( this.keyContents instanceof Backbone.Collection ) {
+					models = this.keyContents.models;
+				}
+				else {
+					// Handle cases the an API/user supplies just an Object/id instead of an Array
+					this.keyContents = _.isArray( this.keyContents ) ? this.keyContents : [ this.keyContents ];
+
+					// Try to find instances of the appropriate 'relatedModel' in the store
+					_.each( this.keyContents, function( item ) {
+							var model = null;
+							if ( item instanceof this.relatedModel ) {
+								model = item;
+							}
+							else if ( item || item === 0 ) { // since 0 can be a valid `id` as well
+								model = this.relatedModel.findOrCreate( item, { create: this.options.createModels } );
+							}
+
+							if ( model && !this.related.getByCid( model ) && !this.related.get( model ) ) {
+								models.push( model );
+							}
+						}, this );
+				}
+
+				// Add all found 'models' in on go, so 'add' will only be called once (and thus 'sort', etc.)
+				if ( models.length ) {
+					options = this.unsanitizeOptions( options );
+					this.related.add( models, options );
+				}
+			}
+		},
+		
+		/**
+		 * If the key is changed, notify old & new reverse relations and initialize the new relation
+		 */
+		onChange: function( model, attr, options ) {
+			options = this.sanitizeOptions( options );
+			this.keyContents = attr;
+			
+			// Notify old 'related' object of the terminated relation
+			_.each( this.getReverseRelations(), function( relation ) {
+					relation.removeRelated( this.instance, options );
+				}, this );
+			
+			// Replace 'this.related' by 'attr' if it is a Backbone.Collection
+			if ( attr instanceof Backbone.Collection ) {
+				this._prepareCollection( attr );
+				this.related = attr;
+			}
+			// Otherwise, 'attr' should be an array of related object ids.
+			// Re-use the current 'this.related' if it is a Backbone.Collection, and remove any current entries.
+			// Otherwise, create a new collection.
+			else {
+				var coll;
+
+				if ( this.related instanceof Backbone.Collection ) {
+					coll = this.related;
+					coll.remove( coll.models );
+				}
+				else {
+					coll = this._prepareCollection();
+				}
+
+				this.setRelated( coll );
+				this.findRelated( options );
+			}
+			
+			// Notify new 'related' object of the new relation
+			_.each( this.getReverseRelations(), function( relation ) {
+					relation.addRelated( this.instance, options );
+				}, this );
+			
+			var dit = this;
+			Backbone.Relational.eventQueue.add( function() {
+				!options.silentChange && dit.instance.trigger( 'update:' + dit.key, dit.instance, dit.related, options );
+			});
+		},
+		
+		tryAddRelated: function( model, options ) {
+			options = this.sanitizeOptions( options );
+			if ( !this.related.getByCid( model ) && !this.related.get( model ) ) {
+				// Check if this new model was specified in 'this.keyContents'
+				var item = _.any( this.keyContents, function( item ) {
+						var id = Backbone.Relational.store.resolveIdForItem( this.relatedModel, item );
+						return !_.isNull( id ) && id === model.id;
+					}, this );
+				
+				if ( item ) {
+					this.related.add( model, options );
+				}
+			}
+		},
+		
+		/**
+		 * When a model is added to a 'HasMany', trigger 'add' on 'this.instance' and notify reverse relations.
+		 * (should be 'HasOne', must set 'this.instance' as their related).
+		 */
+		handleAddition: function( model, coll, options ) {
+			//console.debug('handleAddition called; args=%o', arguments);
+			// Make sure the model is in fact a valid model before continuing.
+			// (it can be invalid as a result of failing validation in Backbone.Collection._prepareModel)
+			if ( !( model instanceof Backbone.Model ) ) {
+				return;
+			}
+			
+			options = this.sanitizeOptions( options );
+			
+			_.each( this.getReverseRelations( model ), function( relation ) {
+					relation.addRelated( this.instance, options );
+				}, this );
+
+			// Only trigger 'add' once the newly added model is initialized (so, has it's relations set up)
+			var dit = this;
+			Backbone.Relational.eventQueue.add( function() {
+				!options.silentChange && dit.instance.trigger( 'add:' + dit.key, model, dit.related, options );
+			});
+		},
+		
+		/**
+		 * When a model is removed from a 'HasMany', trigger 'remove' on 'this.instance' and notify reverse relations.
+		 * (should be 'HasOne', which should be nullified)
+		 */
+		handleRemoval: function( model, coll, options ) {
+			//console.debug('handleRemoval called; args=%o', arguments);
+			if ( !( model instanceof Backbone.Model ) ) {
+				return;
+			}
+
+			options = this.sanitizeOptions( options );
+			
+			_.each( this.getReverseRelations( model ), function( relation ) {
+					relation.removeRelated( this.instance, options );
+				}, this );
+			
+			var dit = this;
+			Backbone.Relational.eventQueue.add( function() {
+				!options.silentChange && dit.instance.trigger( 'remove:' + dit.key, model, dit.related, options );
+			});
+		},
+
+		handleReset: function( coll, options ) {
+			options = this.sanitizeOptions( options );
+
+			var dit = this;
+			Backbone.Relational.eventQueue.add( function() {
+				!options.silentChange && dit.instance.trigger( 'reset:' + dit.key, dit.related, options );
+			});
+		},
+		
+		addRelated: function( model, options ) {
+			var dit = this;
+			options = this.unsanitizeOptions( options );
+			model.queue( function() { // Queued to avoid errors for adding 'model' to the 'this.related' set twice
+				if ( dit.related && !dit.related.getByCid( model ) && !dit.related.get( model ) ) {
+					dit.related.add( model, options );
+				}
+			});
+		},
+		
+		removeRelated: function( model, options ) {
+			options = this.unsanitizeOptions( options );
+			if ( this.related.getByCid( model ) || this.related.get( model ) ) {
+				this.related.remove( model, options );
+			}
+		}
+	});
+	
+	/**
+	 * A type of Backbone.Model that also maintains relations to other models and collections.
+	 * New events when compared to the original:
+	 *  - 'add:<key>' (model, related collection, options)
+	 *  - 'remove:<key>' (model, related collection, options)
+	 *  - 'update:<key>' (model, related model or collection, options)
+	 */
+	Backbone.RelationalModel = Backbone.Model.extend({
+		relations: null, // Relation descriptions on the prototype
+		_relations: null, // Relation instances
+		_isInitialized: false,
+		_deferProcessing: false,
+		_queue: null,
+		
+		subModelTypeAttribute: 'type',
+		subModelTypes: null,
+		
+		constructor: function( attributes, options ) {
+			// Nasty hack, for cases like 'model.get( <HasMany key> ).add( item )'.
+			// Defer 'processQueue', so that when 'Relation.createModels' is used we:
+			// a) Survive 'Backbone.Collection.add'; this takes care we won't error on "can't add model to a set twice"
+			//    (by creating a model from properties, having the model add itself to the collection via one of
+			//    it's relations, then trying to add it to the collection).
+			// b) Trigger 'HasMany' collection events only after the model is really fully set up.
+			// Example that triggers both a and b: "p.get('jobs').add( { company: c, person: p } )".
+			var dit = this;
+			if ( options && options.collection ) {
+				this._deferProcessing = true;
+				
+				var processQueue = function( model ) {
+					if ( model === dit ) {
+						dit._deferProcessing = false;
+						dit.processQueue();
+						options.collection.unbind( 'relational:add', processQueue );
+					}
+				};
+				options.collection.bind( 'relational:add', processQueue );
+				
+				// So we do process the queue eventually, regardless of whether this model really gets added to 'options.collection'.
+				_.defer( function() {
+					processQueue( dit );
+				});
+			}
+			
+			this._queue = new Backbone.BlockingQueue();
+			this._queue.block();
+			Backbone.Relational.eventQueue.block();
+			
+			Backbone.Model.apply( this, arguments );
+			
+			// Try to run the global queue holding external events
+			Backbone.Relational.eventQueue.unblock();
+		},
+		
+		/**
+		 * Override 'trigger' to queue 'change' and 'change:*' events
+		 */
+		trigger: function( eventName ) {
+			if ( eventName.length > 5 && 'change' === eventName.substr( 0, 6 ) ) {
+				var dit = this, args = arguments;
+				Backbone.Relational.eventQueue.add( function() {
+						Backbone.Model.prototype.trigger.apply( dit, args );
+					});
+			}
+			else {
+				Backbone.Model.prototype.trigger.apply( this, arguments );
+			}
+			
+			return this;
+		},
+		
+		/**
+		 * Initialize Relations present in this.relations; determine the type (HasOne/HasMany), then creates a new instance.
+		 * Invoked in the first call so 'set' (which is made from the Backbone.Model constructor).
+		 */
+		initializeRelations: function() {
+			this.acquire(); // Setting up relations often also involve calls to 'set', and we only want to enter this function once
+			this._relations = [];
+			
+			_.each( this.relations, function( rel ) {
+					var type = !_.isString( rel.type ) ? rel.type :	Backbone[ rel.type ] || Backbone.Relational.store.getObjectByName( rel.type );
+					if ( type && type.prototype instanceof Backbone.Relation ) {
+						new type( this, rel ); // Also pushes the new Relation into _relations
+					}
+					else {
+						Backbone.Relational.showWarnings && typeof console !== 'undefined' && console.warn( 'Relation=%o; missing or invalid type!', rel );
+					}
+				}, this );
+			
+			this._isInitialized = true;
+			this.release();
+			this.processQueue();
+		},
+
+		/**
+		 * When new values are set, notify this model's relations (also if options.silent is set).
+		 * (Relation.setRelated locks this model before calling 'set' on it to prevent loops)
+		 */
+		updateRelations: function( options ) {
+			if ( this._isInitialized && !this.isLocked() ) {
+				_.each( this._relations, function( rel ) {
+					// Update from data in `rel.keySource` if set, or `rel.key` otherwise
+					var val = this.attributes[ rel.keySource ] || this.attributes[ rel.key ];
+					if ( rel.related !== val ) {
+						this.trigger( 'relational:change:' + rel.key, this, val, options || {} );
+					}
+				}, this );
+			}
+		},
+		
+		/**
+		 * Either add to the queue (if we're not initialized yet), or execute right away.
+		 */
+		queue: function( func ) {
+			this._queue.add( func );
+		},
+		
+		/**
+		 * Process _queue
+		 */
+		processQueue: function() {
+			if ( this._isInitialized && !this._deferProcessing && this._queue.isBlocked() ) {
+				this._queue.unblock();
+			}
+		},
+		
+		/**
+		 * Get a specific relation.
+		 * @param key {string} The relation key to look for.
+		 * @return {Backbone.Relation} An instance of 'Backbone.Relation', if a relation was found for 'key', or null.
+		 */
+		getRelation: function( key ) {
+			return _.detect( this._relations, function( rel ) {
+				if ( rel.key === key ) {
+					return true;
+				}
+			}, this );
+		},
+		
+		/**
+		 * Get all of the created relations.
+		 * @return {Backbone.Relation[]}
+		 */
+		getRelations: function() {
+			return this._relations;
+		},
+		
+		/**
+		 * Retrieve related objects.
+		 * @param key {string} The relation key to fetch models for.
+		 * @param [options] {Object} Options for 'Backbone.Model.fetch' and 'Backbone.sync'.
+		 * @param [update=false] {boolean} Whether to force a fetch from the server (updating existing models).
+		 * @return {jQuery.when[]} An array of request objects
+		 */
+		fetchRelated: function( key, options, update ) {
+			options || ( options = {} );
+			var setUrl,
+				requests = [],
+				rel = this.getRelation( key ),
+				keyContents = rel && rel.keyContents,
+				toFetch = keyContents && _.select( _.isArray( keyContents ) ? keyContents : [ keyContents ], function( item ) {
+					var id = Backbone.Relational.store.resolveIdForItem( rel.relatedModel, item );
+					return !_.isNull( id ) && ( update || !Backbone.Relational.store.find( rel.relatedModel, id ) );
+				}, this );
+			
+			if ( toFetch && toFetch.length ) {
+				// Create a model for each entry in 'keyContents' that is to be fetched
+				var models = _.map( toFetch, function( item ) {
+					var model;
+
+					if ( _.isObject( item ) ) {
+						model = rel.relatedModel.build( item );
+					}
+					else {
+						var attrs = {};
+						attrs[ rel.relatedModel.prototype.idAttribute ] = item;
+						model = rel.relatedModel.build( attrs );
+					}
+
+					return model;
+				}, this );
+				
+				// Try if the 'collection' can provide a url to fetch a set of models in one request.
+				if ( rel.related instanceof Backbone.Collection && _.isFunction( rel.related.url ) ) {
+					setUrl = rel.related.url( models );
+				}
+				
+				// An assumption is that when 'Backbone.Collection.url' is a function, it can handle building of set urls.
+				// To make sure it can, test if the url we got by supplying a list of models to fetch is different from
+				// the one supplied for the default fetch action (without args to 'url').
+				if ( setUrl && setUrl !== rel.related.url() ) {
+					var opts = _.defaults(
+						{
+							error: function() {
+								var args = arguments;
+								_.each( models, function( model ) {
+										model.trigger( 'destroy', model, model.collection, options );
+										options.error && options.error.apply( model, args );
+									});
+							},
+							url: setUrl
+						},
+						options,
+						{ add: true }
+					);
+
+					requests = [ rel.related.fetch( opts ) ];
+				}
+				else {
+					requests = _.map( models, function( model ) {
+						var opts = _.defaults(
+							{
+								error: function() {
+									model.trigger( 'destroy', model, model.collection, options );
+									options.error && options.error.apply( model, arguments );
+								}
+							},
+							options
+						);
+						return model.fetch( opts );
+					}, this );
+				}
+			}
+			
+			return requests;
+		},
+		
+		set: function( key, value, options ) {
+			Backbone.Relational.eventQueue.block();
+			
+			// Duplicate backbone's behavior to allow separate key/value parameters, instead of a single 'attributes' object
+			var attributes;
+			if ( _.isObject( key ) || key == null ) {
+				attributes = key;
+				options = value;
+			}
+			else {
+				attributes = {};
+				attributes[ key ] = value;
+			}
+			
+			var result = Backbone.Model.prototype.set.apply( this, arguments );
+			
+			// Ideal place to set up relations :)
+			if ( !this._isInitialized && !this.isLocked() ) {
+				this.constructor.initializeModelHierarchy();
+
+				Backbone.Relational.store.register( this );
+
+				this.initializeRelations();
+			}
+			// Update the 'idAttribute' in Backbone.store if; we don't want it to miss an 'id' update due to {silent:true}
+			else if ( attributes && this.idAttribute in attributes ) {
+				Backbone.Relational.store.update( this );
+			}
+			
+			if ( attributes ) {
+				this.updateRelations( options );
+			}
+			
+			// Try to run the global queue holding external events
+			Backbone.Relational.eventQueue.unblock();
+			
+			return result;
+		},
+		
+		unset: function( attribute, options ) {
+			Backbone.Relational.eventQueue.block();
+			
+			var result = Backbone.Model.prototype.unset.apply( this, arguments );
+			this.updateRelations( options );
+			
+			// Try to run the global queue holding external events
+			Backbone.Relational.eventQueue.unblock();
+			
+			return result;
+		},
+		
+		clear: function( options ) {
+			Backbone.Relational.eventQueue.block();
+			
+			var result = Backbone.Model.prototype.clear.apply( this, arguments );
+			this.updateRelations( options );
+			
+			// Try to run the global queue holding external events
+			Backbone.Relational.eventQueue.unblock();
+			
+			return result;
+		},
+		
+		/**
+		 * Override 'change', so the change will only execute after 'set' has finised (relations are updated),
+		 * and 'previousAttributes' will be available when the event is fired.
+		 */
+		change: function( options ) {
+			var dit = this, args = arguments;
+			Backbone.Relational.eventQueue.add( function() {
+					Backbone.Model.prototype.change.apply( dit, args );
+				});
+		},
+
+		clone: function() {
+			var attributes = _.clone( this.attributes );
+			if ( !_.isUndefined( attributes[ this.idAttribute ] ) ) {
+				attributes[ this.idAttribute ] = null;
+			}
+
+			_.each( this.getRelations(), function( rel ) {
+					delete attributes[ rel.key ];
+				});
+
+			return new this.constructor( attributes );
+		},
+		
+		/**
+		 * Convert relations to JSON, omits them when required
+		 */
+		toJSON: function() {
+			// If this Model has already been fully serialized in this branch once, return to avoid loops
+			if ( this.isLocked() ) {
+				return this.id;
+			}
+			
+			this.acquire();
+			var json = Backbone.Model.prototype.toJSON.call( this );
+			
+			if ( this.constructor._superModel && !( this.constructor._subModelTypeAttribute in json ) ) {
+				json[ this.constructor._subModelTypeAttribute ] = this.constructor._subModelTypeValue;
+			}
+			
+			_.each( this._relations, function( rel ) {
+					var value = json[ rel.key ];
+
+					if ( rel.options.includeInJSON === true) {
+						if ( value && _.isFunction( value.toJSON ) ) {
+							json[ rel.keyDestination ] = value.toJSON();
+						}
+						else {
+							json[ rel.keyDestination ] = null;
+						}
+					}
+					else if ( _.isString( rel.options.includeInJSON ) ) {
+						if ( value instanceof Backbone.Collection ) {
+							json[ rel.keyDestination ] = value.pluck( rel.options.includeInJSON );
+						}
+						else if ( value instanceof Backbone.Model ) {
+							json[ rel.keyDestination ] = value.get( rel.options.includeInJSON );
+						}	
+						else {
+							json[ rel.keyDestination ] = null;
+						}
+					}
+					else if ( _.isArray( rel.options.includeInJSON ) ) {
+						if ( value instanceof Backbone.Collection ) {
+							var valueSub = [];
+							value.each( function( model ) {
+								var curJson = {};
+								_.each( rel.options.includeInJSON, function( key ) {
+									curJson[ key ] = model.get( key );
+								});
+								valueSub.push( curJson );
+							});
+							json[ rel.keyDestination ] = valueSub;
+						}
+						else if ( value instanceof Backbone.Model ) {
+							var valueSub = {};
+							_.each( rel.options.includeInJSON, function( key ) {
+								valueSub[ key ] = value.get( key );
+							});
+							json[ rel.keyDestination ] = valueSub;
+						}
+						else {
+							json[ rel.keyDestination ] = null;
+						}
+					}
+					else {
+						delete json[ rel.key ];
+					}
+
+					if ( rel.keyDestination !== rel.key ) {
+						delete json[ rel.key ];
+					}
+				});
+			
+			this.release();
+			return json;
+		}
+	},
+	{
+		setup: function( superModel ) {
+			// We don't want to share a relations array with a parent, as this will cause problems with
+			// reverse relations.
+			this.prototype.relations = ( this.prototype.relations || [] ).slice( 0 );
+
+			this._subModels = {};
+			this._superModel = null;
+
+			// If this model has 'subModelTypes' itself, remember them in the store
+			if ( this.prototype.hasOwnProperty( 'subModelTypes' ) ) {
+				Backbone.Relational.store.addSubModels( this.prototype.subModelTypes, this );
+			}
+			// The 'subModelTypes' property should not be inherited, so reset it.
+			else {
+				this.prototype.subModelTypes = null;
+			}
+
+			// Initialize all reverseRelations that belong to this new model.
+			_.each( this.prototype.relations, function( rel ) {
+					if ( !rel.model ) {
+						rel.model = this;
+					}
+
+					if ( rel.reverseRelation && rel.model === this ) {				
+						var preInitialize = true;
+						if ( _.isString( rel.relatedModel ) ) {
+							/**
+							 * The related model might not be defined for two reasons
+							 *  1. it never gets defined, e.g. a typo
+							 *  2. it is related to itself
+							 * In neither of these cases do we need to pre-initialize reverse relations.
+							 */
+							var relatedModel = Backbone.Relational.store.getObjectByName( rel.relatedModel );
+							preInitialize = relatedModel && ( relatedModel.prototype instanceof Backbone.RelationalModel );
+						}
+
+						var type = !_.isString( rel.type ) ? rel.type : Backbone[ rel.type ] || Backbone.Relational.store.getObjectByName( rel.type );
+						if ( preInitialize && type && type.prototype instanceof Backbone.Relation ) {
+							new type( null, rel );
+						}
+					}
+				}, this );
+			
+			return this;
+		},
+
+		/**
+		 * Create a 'Backbone.Model' instance based on 'attributes'.
+		 * @param {Object} attributes
+		 * @param {Object} [options]
+		 * @return {Backbone.Model}
+		 */
+		build: function( attributes, options ) {
+			var model = this;
+
+			// 'build' is a possible entrypoint; it's possible no model hierarchy has been determined yet.
+			this.initializeModelHierarchy();
+
+			// Determine what type of (sub)model should be built if applicable.
+			// Lookup the proper subModelType in 'this._subModels'.
+			if ( this._subModels && this.prototype.subModelTypeAttribute in attributes ) {
+				var subModelTypeAttribute = attributes[ this.prototype.subModelTypeAttribute ];
+				var subModelType = this._subModels[ subModelTypeAttribute ];
+				if ( subModelType ) {
+					model = subModelType;
+				}
+			}
+			
+			return new model( attributes, options );
+		},
+
+		initializeModelHierarchy: function() {
+			// If we're here for the first time, try to determine if this modelType has a 'superModel'.
+			if ( _.isUndefined( this._superModel ) || _.isNull( this._superModel ) ) {
+				Backbone.Relational.store.setupSuperModel( this );
+
+				// If a superModel has been found, copy relations from the _superModel if they haven't been
+				// inherited automatically (due to a redefinition of 'relations').
+				// Otherwise, make sure we don't get here again for this type by making '_superModel' false so we fail
+				// the isUndefined/isNull check next time.
+				if ( this._superModel ) {
+					//
+					if ( this._superModel.prototype.relations ) {
+						var supermodelRelationsExist = _.any( this.prototype.relations, function( rel ) {
+							return rel.model && rel.model !== this;
+						}, this );
+
+						if ( !supermodelRelationsExist ) {
+							this.prototype.relations = this._superModel.prototype.relations.concat( this.prototype.relations );
+						}
+					}
+				}
+				else {
+					this._superModel = false;
+				}
+			}
+
+			// If we came here through 'build' for a model that has 'subModelTypes', and not all of them have been resolved yet, try to resolve each.
+			if ( this.prototype.subModelTypes && _.keys( this.prototype.subModelTypes ).length !== _.keys( this._subModels ).length ) {
+				_.each( this.prototype.subModelTypes, function( subModelTypeName ) {
+					var subModelType = Backbone.Relational.store.getObjectByName( subModelTypeName );
+					subModelType && subModelType.initializeModelHierarchy();
+				});
+			}
+		},
+
+		/**
+		 * Find an instance of `this` type in 'Backbone.Relational.store'.
+		 * - If `attributes` is a string or a number, `findOrCreate` will just query the `store` and return a model if found.
+		 * - If `attributes` is an object, the model will be updated with `attributes` if found.
+		 *   Otherwise, a new model is created with `attributes` (unless `options.create` is explicitly set to `false`).
+		 * @param {Object|String|Number} attributes Either a model's id, or the attributes used to create or update a model.
+		 * @param {Object} [options]
+		 * @param {Boolean} [options.create=true]
+		 * @return {Backbone.RelationalModel}
+		 */
+		findOrCreate: function( attributes, options ) {
+			// Try to find an instance of 'this' model type in the store
+			var model = Backbone.Relational.store.find( this, attributes );
+
+			// If we found an instance, update it with the data in 'item'; if not, create an instance
+			// (unless 'options.create' is false).
+			if ( _.isObject( attributes ) ) {
+				if ( model ) {
+					model.set( model.parse ? model.parse( attributes ) : attributes, options );
+				}
+				else if ( !options || ( options && options.create !== false ) ) {
+					model = this.build( attributes, options );
+				}
+			}
+
+			return model;
+		}
+	});
+	_.extend( Backbone.RelationalModel.prototype, Backbone.Semaphore );
+	
+	/**
+	 * Override Backbone.Collection._prepareModel, so objects will be built using the correct type
+	 * if the collection.model has subModels.
+	 */
+	Backbone.Collection.prototype.__prepareModel = Backbone.Collection.prototype._prepareModel;
+	Backbone.Collection.prototype._prepareModel = function ( model, options ) {
+		options || (options = {});
+		if ( !( model instanceof Backbone.Model ) ) {
+			var attrs = model;
+			options.collection = this;
+
+			if ( typeof this.model.findOrCreate !== 'undefined' ) {
+				model = this.model.findOrCreate( attrs, options );
+			}
+			else {
+				model = new this.model( attrs, options );
+			}
+			
+			if ( !model._validate( model.attributes, options ) ) {
+				model = false;
+			}
+		}
+		else if ( !model.collection ) {
+			model.collection = this;
+		}
+		
+		return model;
+	}
+	
+	/**
+	 * Override Backbone.Collection.add, so objects fetched from the server multiple times will
+	 * update the existing Model. Also, trigger 'relational:add'.
+	 */
+	var add = Backbone.Collection.prototype.__add = Backbone.Collection.prototype.add;
+	Backbone.Collection.prototype.add = function( models, options ) {
+		options || (options = {});
+		if ( !_.isArray( models ) ) {
+			models = [ models ];
+		}
+
+		var modelsToAdd = [];
+
+		//console.debug( 'calling add on coll=%o; model=%o, options=%o', this, models, options );
+		_.each( models, function( model ) {
+			if ( !( model instanceof Backbone.Model ) ) {
+				// `_prepareModel` attempts to find `model` in Backbone.store through `findOrCreate`,
+				// and sets the new properties on it if is found. Otherwise, a new model is instantiated.
+				model = Backbone.Collection.prototype._prepareModel.call( this, model, options );
+			}
+
+			if ( model instanceof Backbone.Model && !this.get( model ) && !this.getByCid( model ) ) {
+				modelsToAdd.push( model );
+			}
+		}, this );
+
+
+		// Add 'models' in a single batch, so the original add will only be called once (and thus 'sort', etc).
+		if ( modelsToAdd.length ) {
+			add.call( this, modelsToAdd, options );
+
+			_.each( modelsToAdd, function( model ) {
+				this.trigger( 'relational:add', model, this, options );
+			}, this );
+		}
+		
+		return this;
+	};
+	
+	/**
+	 * Override 'Backbone.Collection.remove' to trigger 'relational:remove'.
+	 */
+	var remove = Backbone.Collection.prototype.__remove = Backbone.Collection.prototype.remove;
+	Backbone.Collection.prototype.remove = function( models, options ) {
+		options || (options = {});
+		if ( !_.isArray( models ) ) {
+			models = [ models ];
+		}
+		else {
+			models = models.slice( 0 );
+		}
+
+		//console.debug('calling remove on coll=%o; models=%o, options=%o', this, models, options );
+		_.each( models, function( model ) {
+				model = this.getByCid( model ) || this.get( model );
+
+				if ( model instanceof Backbone.Model ) {
+					remove.call( this, model, options );
+					this.trigger('relational:remove', model, this, options);
+				}
+			}, this );
+		
+		return this;
+	};
+
+	/**
+	 * Override 'Backbone.Collection.reset' to trigger 'relational:reset'.
+	 */
+	var reset = Backbone.Collection.prototype.__reset = Backbone.Collection.prototype.reset;
+	Backbone.Collection.prototype.reset = function( models, options ) {
+		reset.call( this, models, options );
+		this.trigger( 'relational:reset', this, options );
+
+		return this;
+	};
+
+	/**
+	 * Override 'Backbone.Collection.sort' to trigger 'relational:reset'.
+	 */
+	var sort = Backbone.Collection.prototype.__sort = Backbone.Collection.prototype.sort;
+	Backbone.Collection.prototype.sort = function( options ) {
+		sort.call( this, options );
+		this.trigger( 'relational:reset', this, options );
+
+		return this;
+	};
+	
+	/**
+	 * Override 'Backbone.Collection.trigger' so 'add', 'remove' and 'reset' events are queued until relations
+	 * are ready.
+	 */
+	var trigger = Backbone.Collection.prototype.__trigger = Backbone.Collection.prototype.trigger;
+	Backbone.Collection.prototype.trigger = function( eventName ) {
+		if ( eventName === 'add' || eventName === 'remove' || eventName === 'reset' ) {
+			var dit = this, args = arguments;
+			
+			if (eventName === 'add') {
+				args = _.toArray(args);
+				// the fourth argument in case of a regular add is the option object.
+				// we need to clone it, as it could be modified while we wait on the eventQueue to be unblocked
+				if (_.isObject(args[3])) {
+					args[3] = _.clone(args[3]);
+				}
+			}
+			
+			Backbone.Relational.eventQueue.add( function() {
+					trigger.apply( dit, args );
+				});
+		}
+		else {
+			trigger.apply( this, arguments );
+		}
+		
+		return this;
+	};
+
+	// Override .extend() to automatically call .setup()
+	Backbone.RelationalModel.extend = function( protoProps, classProps ) {
+		var child = Backbone.Model.extend.apply( this, arguments );
+		
+		child.setup( this );
+
+		return child;
+	};
+})();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/metadataplayer/libs/backbone.js	Thu Sep 27 12:23:41 2012 +0200
@@ -0,0 +1,1431 @@
+//     Backbone.js 0.9.2
+
+//     (c) 2010-2012 Jeremy Ashkenas, DocumentCloud Inc.
+//     Backbone may be freely distributed under the MIT license.
+//     For all details and documentation:
+//     http://backbonejs.org
+
+(function(){
+
+  // Initial Setup
+  // -------------
+
+  // Save a reference to the global object (`window` in the browser, `global`
+  // on the server).
+  var root = this;
+
+  // Save the previous value of the `Backbone` variable, so that it can be
+  // restored later on, if `noConflict` is used.
+  var previousBackbone = root.Backbone;
+
+  // Create a local reference to slice/splice.
+  var slice = Array.prototype.slice;
+  var splice = Array.prototype.splice;
+
+  // The top-level namespace. All public Backbone classes and modules will
+  // be attached to this. Exported for both CommonJS and the browser.
+  var Backbone;
+  if (typeof exports !== 'undefined') {
+    Backbone = exports;
+  } else {
+    Backbone = root.Backbone = {};
+  }
+
+  // Current version of the library. Keep in sync with `package.json`.
+  Backbone.VERSION = '0.9.2';
+
+  // Require Underscore, if we're on the server, and it's not already present.
+  var _ = root._;
+  if (!_ && (typeof require !== 'undefined')) _ = require('underscore');
+
+  // For Backbone's purposes, jQuery, Zepto, or Ender owns the `$` variable.
+  var $ = root.jQuery || root.Zepto || root.ender;
+
+  // Set the JavaScript library that will be used for DOM manipulation and
+  // Ajax calls (a.k.a. the `$` variable). By default Backbone will use: jQuery,
+  // Zepto, or Ender; but the `setDomLibrary()` method lets you inject an
+  // alternate JavaScript library (or a mock library for testing your views
+  // outside of a browser).
+  Backbone.setDomLibrary = function(lib) {
+    $ = lib;
+  };
+
+  // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable
+  // to its previous owner. Returns a reference to this Backbone object.
+  Backbone.noConflict = function() {
+    root.Backbone = previousBackbone;
+    return this;
+  };
+
+  // Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option
+  // will fake `"PUT"` and `"DELETE"` requests via the `_method` parameter and
+  // set a `X-Http-Method-Override` header.
+  Backbone.emulateHTTP = false;
+
+  // Turn on `emulateJSON` to support legacy servers that can't deal with direct
+  // `application/json` requests ... will encode the body as
+  // `application/x-www-form-urlencoded` instead and will send the model in a
+  // form param named `model`.
+  Backbone.emulateJSON = false;
+
+  // Backbone.Events
+  // -----------------
+
+  // Regular expression used to split event strings
+  var eventSplitter = /\s+/;
+
+  // A module that can be mixed in to *any object* in order to provide it with
+  // custom events. You may bind with `on` or remove with `off` callback functions
+  // to an event; trigger`-ing an event fires all callbacks in succession.
+  //
+  //     var object = {};
+  //     _.extend(object, Backbone.Events);
+  //     object.on('expand', function(){ alert('expanded'); });
+  //     object.trigger('expand');
+  //
+  var Events = Backbone.Events = {
+
+    // Bind one or more space separated events, `events`, to a `callback`
+    // function. Passing `"all"` will bind the callback to all events fired.
+    on: function(events, callback, context) {
+
+      var calls, event, node, tail, list;
+      if (!callback) return this;
+      events = events.split(eventSplitter);
+      calls = this._callbacks || (this._callbacks = {});
+
+      // Create an immutable callback list, allowing traversal during
+      // modification.  The tail is an empty object that will always be used
+      // as the next node.
+      while (event = events.shift()) {
+        list = calls[event];
+        node = list ? list.tail : {};
+        node.next = tail = {};
+        node.context = context;
+        node.callback = callback;
+        calls[event] = {tail: tail, next: list ? list.next : node};
+      }
+
+      return this;
+    },
+
+    // Remove one or many callbacks. If `context` is null, removes all callbacks
+    // with that function. If `callback` is null, removes all callbacks for the
+    // event. If `events` is null, removes all bound callbacks for all events.
+    off: function(events, callback, context) {
+      var event, calls, node, tail, cb, ctx;
+
+      // No events, or removing *all* events.
+      if (!(calls = this._callbacks)) return;
+      if (!(events || callback || context)) {
+        delete this._callbacks;
+        return this;
+      }
+
+      // Loop through the listed events and contexts, splicing them out of the
+      // linked list of callbacks if appropriate.
+      events = events ? events.split(eventSplitter) : _.keys(calls);
+      while (event = events.shift()) {
+        node = calls[event];
+        delete calls[event];
+        if (!node || !(callback || context)) continue;
+        // Create a new list, omitting the indicated callbacks.
+        tail = node.tail;
+        while ((node = node.next) !== tail) {
+          cb = node.callback;
+          ctx = node.context;
+          if ((callback && cb !== callback) || (context && ctx !== context)) {
+            this.on(event, cb, ctx);
+          }
+        }
+      }
+
+      return this;
+    },
+
+    // Trigger one or many events, firing all bound callbacks. Callbacks are
+    // passed the same arguments as `trigger` is, apart from the event name
+    // (unless you're listening on `"all"`, which will cause your callback to
+    // receive the true name of the event as the first argument).
+    trigger: function(events) {
+      var event, node, calls, tail, args, all, rest;
+      if (!(calls = this._callbacks)) return this;
+      all = calls.all;
+      events = events.split(eventSplitter);
+      rest = slice.call(arguments, 1);
+
+      // For each event, walk through the linked list of callbacks twice,
+      // first to trigger the event, then to trigger any `"all"` callbacks.
+      while (event = events.shift()) {
+        if (node = calls[event]) {
+          tail = node.tail;
+          while ((node = node.next) !== tail) {
+            node.callback.apply(node.context || this, rest);
+          }
+        }
+        if (node = all) {
+          tail = node.tail;
+          args = [event].concat(rest);
+          while ((node = node.next) !== tail) {
+            node.callback.apply(node.context || this, args);
+          }
+        }
+      }
+
+      return this;
+    }
+
+  };
+
+  // Aliases for backwards compatibility.
+  Events.bind   = Events.on;
+  Events.unbind = Events.off;
+
+  // Backbone.Model
+  // --------------
+
+  // Create a new model, with defined attributes. A client id (`cid`)
+  // is automatically generated and assigned for you.
+  var Model = Backbone.Model = function(attributes, options) {
+    var defaults;
+    attributes || (attributes = {});
+    if (options && options.parse) attributes = this.parse(attributes);
+    if (defaults = getValue(this, 'defaults')) {
+      attributes = _.extend({}, defaults, attributes);
+    }
+    if (options && options.collection) this.collection = options.collection;
+    this.attributes = {};
+    this._escapedAttributes = {};
+    this.cid = _.uniqueId('c');
+    this.changed = {};
+    this._silent = {};
+    this._pending = {};
+    this.set(attributes, {silent: true});
+    // Reset change tracking.
+    this.changed = {};
+    this._silent = {};
+    this._pending = {};
+    this._previousAttributes = _.clone(this.attributes);
+    this.initialize.apply(this, arguments);
+  };
+
+  // Attach all inheritable methods to the Model prototype.
+  _.extend(Model.prototype, Events, {
+
+    // A hash of attributes whose current and previous value differ.
+    changed: null,
+
+    // A hash of attributes that have silently changed since the last time
+    // `change` was called.  Will become pending attributes on the next call.
+    _silent: null,
+
+    // A hash of attributes that have changed since the last `'change'` event
+    // began.
+    _pending: null,
+
+    // The default name for the JSON `id` attribute is `"id"`. MongoDB and
+    // CouchDB users may want to set this to `"_id"`.
+    idAttribute: 'id',
+
+    // Initialize is an empty function by default. Override it with your own
+    // initialization logic.
+    initialize: function(){},
+
+    // Return a copy of the model's `attributes` object.
+    toJSON: function(options) {
+      return _.clone(this.attributes);
+    },
+
+    // Get the value of an attribute.
+    get: function(attr) {
+      return this.attributes[attr];
+    },
+
+    // Get the HTML-escaped value of an attribute.
+    escape: function(attr) {
+      var html;
+      if (html = this._escapedAttributes[attr]) return html;
+      var val = this.get(attr);
+      return this._escapedAttributes[attr] = _.escape(val == null ? '' : '' + val);
+    },
+
+    // Returns `true` if the attribute contains a value that is not null
+    // or undefined.
+    has: function(attr) {
+      return this.get(attr) != null;
+    },
+
+    // Set a hash of model attributes on the object, firing `"change"` unless
+    // you choose to silence it.
+    set: function(key, value, options) {
+      var attrs, attr, val;
+
+      // Handle both `"key", value` and `{key: value}` -style arguments.
+      if (_.isObject(key) || key == null) {
+        attrs = key;
+        options = value;
+      } else {
+        attrs = {};
+        attrs[key] = value;
+      }
+
+      // Extract attributes and options.
+      options || (options = {});
+      if (!attrs) return this;
+      if (attrs instanceof Model) attrs = attrs.attributes;
+      if (options.unset) for (attr in attrs) attrs[attr] = void 0;
+
+      // Run validation.
+      if (!this._validate(attrs, options)) return false;
+
+      // Check for changes of `id`.
+      if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
+
+      var changes = options.changes = {};
+      var now = this.attributes;
+      var escaped = this._escapedAttributes;
+      var prev = this._previousAttributes || {};
+
+      // For each `set` attribute...
+      for (attr in attrs) {
+        val = attrs[attr];
+
+        // If the new and current value differ, record the change.
+        if (!_.isEqual(now[attr], val) || (options.unset && _.has(now, attr))) {
+          delete escaped[attr];
+          (options.silent ? this._silent : changes)[attr] = true;
+        }
+
+        // Update or delete the current value.
+        options.unset ? delete now[attr] : now[attr] = val;
+
+        // If the new and previous value differ, record the change.  If not,
+        // then remove changes for this attribute.
+        if (!_.isEqual(prev[attr], val) || (_.has(now, attr) != _.has(prev, attr))) {
+          this.changed[attr] = val;
+          if (!options.silent) this._pending[attr] = true;
+        } else {
+          delete this.changed[attr];
+          delete this._pending[attr];
+        }
+      }
+
+      // Fire the `"change"` events.
+      if (!options.silent) this.change(options);
+      return this;
+    },
+
+    // Remove an attribute from the model, firing `"change"` unless you choose
+    // to silence it. `unset` is a noop if the attribute doesn't exist.
+    unset: function(attr, options) {
+      (options || (options = {})).unset = true;
+      return this.set(attr, null, options);
+    },
+
+    // Clear all attributes on the model, firing `"change"` unless you choose
+    // to silence it.
+    clear: function(options) {
+      (options || (options = {})).unset = true;
+      return this.set(_.clone(this.attributes), options);
+    },
+
+    // Fetch the model from the server. If the server's representation of the
+    // model differs from its current attributes, they will be overriden,
+    // triggering a `"change"` event.
+    fetch: function(options) {
+      options = options ? _.clone(options) : {};
+      var model = this;
+      var success = options.success;
+      options.success = function(resp, status, xhr) {
+        if (!model.set(model.parse(resp, xhr), options)) return false;
+        if (success) success(model, resp);
+      };
+      options.error = Backbone.wrapError(options.error, model, options);
+      return (this.sync || Backbone.sync).call(this, 'read', this, options);
+    },
+
+    // Set a hash of model attributes, and sync the model to the server.
+    // If the server returns an attributes hash that differs, the model's
+    // state will be `set` again.
+    save: function(key, value, options) {
+      var attrs, current;
+
+      // Handle both `("key", value)` and `({key: value})` -style calls.
+      if (_.isObject(key) || key == null) {
+        attrs = key;
+        options = value;
+      } else {
+        attrs = {};
+        attrs[key] = value;
+      }
+      options = options ? _.clone(options) : {};
+
+      // If we're "wait"-ing to set changed attributes, validate early.
+      if (options.wait) {
+        if (!this._validate(attrs, options)) return false;
+        current = _.clone(this.attributes);
+      }
+
+      // Regular saves `set` attributes before persisting to the server.
+      var silentOptions = _.extend({}, options, {silent: true});
+      if (attrs && !this.set(attrs, options.wait ? silentOptions : options)) {
+        return false;
+      }
+
+      // After a successful server-side save, the client is (optionally)
+      // updated with the server-side state.
+      var model = this;
+      var success = options.success;
+      options.success = function(resp, status, xhr) {
+        var serverAttrs = model.parse(resp, xhr);
+        if (options.wait) {
+          delete options.wait;
+          serverAttrs = _.extend(attrs || {}, serverAttrs);
+        }
+        if (!model.set(serverAttrs, options)) return false;
+        if (success) {
+          success(model, resp);
+        } else {
+          model.trigger('sync', model, resp, options);
+        }
+      };
+
+      // Finish configuring and sending the Ajax request.
+      options.error = Backbone.wrapError(options.error, model, options);
+      var method = this.isNew() ? 'create' : 'update';
+      var xhr = (this.sync || Backbone.sync).call(this, method, this, options);
+      if (options.wait) this.set(current, silentOptions);
+      return xhr;
+    },
+
+    // Destroy this model on the server if it was already persisted.
+    // Optimistically removes the model from its collection, if it has one.
+    // If `wait: true` is passed, waits for the server to respond before removal.
+    destroy: function(options) {
+      options = options ? _.clone(options) : {};
+      var model = this;
+      var success = options.success;
+
+      var triggerDestroy = function() {
+        model.trigger('destroy', model, model.collection, options);
+      };
+
+      if (this.isNew()) {
+        triggerDestroy();
+        return false;
+      }
+
+      options.success = function(resp) {
+        if (options.wait) triggerDestroy();
+        if (success) {
+          success(model, resp);
+        } else {
+          model.trigger('sync', model, resp, options);
+        }
+      };
+
+      options.error = Backbone.wrapError(options.error, model, options);
+      var xhr = (this.sync || Backbone.sync).call(this, 'delete', this, options);
+      if (!options.wait) triggerDestroy();
+      return xhr;
+    },
+
+    // Default URL for the model's representation on the server -- if you're
+    // using Backbone's restful methods, override this to change the endpoint
+    // that will be called.
+    url: function() {
+      var base = getValue(this, 'urlRoot') || getValue(this.collection, 'url') || urlError();
+      if (this.isNew()) return base;
+      return base + (base.charAt(base.length - 1) == '/' ? '' : '/') + encodeURIComponent(this.id);
+    },
+
+    // **parse** converts a response into the hash of attributes to be `set` on
+    // the model. The default implementation is just to pass the response along.
+    parse: function(resp, xhr) {
+      return resp;
+    },
+
+    // Create a new model with identical attributes to this one.
+    clone: function() {
+      return new this.constructor(this.attributes);
+    },
+
+    // A model is new if it has never been saved to the server, and lacks an id.
+    isNew: function() {
+      return this.id == null;
+    },
+
+    // Call this method to manually fire a `"change"` event for this model and
+    // a `"change:attribute"` event for each changed attribute.
+    // Calling this will cause all objects observing the model to update.
+    change: function(options) {
+      options || (options = {});
+      var changing = this._changing;
+      this._changing = true;
+
+      // Silent changes become pending changes.
+      for (var attr in this._silent) this._pending[attr] = true;
+
+      // Silent changes are triggered.
+      var changes = _.extend({}, options.changes, this._silent);
+      this._silent = {};
+      for (var attr in changes) {
+        this.trigger('change:' + attr, this, this.get(attr), options);
+      }
+      if (changing) return this;
+
+      // Continue firing `"change"` events while there are pending changes.
+      while (!_.isEmpty(this._pending)) {
+        this._pending = {};
+        this.trigger('change', this, options);
+        // Pending and silent changes still remain.
+        for (var attr in this.changed) {
+          if (this._pending[attr] || this._silent[attr]) continue;
+          delete this.changed[attr];
+        }
+        this._previousAttributes = _.clone(this.attributes);
+      }
+
+      this._changing = false;
+      return this;
+    },
+
+    // Determine if the model has changed since the last `"change"` event.
+    // If you specify an attribute name, determine if that attribute has changed.
+    hasChanged: function(attr) {
+      if (!arguments.length) return !_.isEmpty(this.changed);
+      return _.has(this.changed, attr);
+    },
+
+    // Return an object containing all the attributes that have changed, or
+    // false if there are no changed attributes. Useful for determining what
+    // parts of a view need to be updated and/or what attributes need to be
+    // persisted to the server. Unset attributes will be set to undefined.
+    // You can also pass an attributes object to diff against the model,
+    // determining if there *would be* a change.
+    changedAttributes: function(diff) {
+      if (!diff) return this.hasChanged() ? _.clone(this.changed) : false;
+      var val, changed = false, old = this._previousAttributes;
+      for (var attr in diff) {
+        if (_.isEqual(old[attr], (val = diff[attr]))) continue;
+        (changed || (changed = {}))[attr] = val;
+      }
+      return changed;
+    },
+
+    // Get the previous value of an attribute, recorded at the time the last
+    // `"change"` event was fired.
+    previous: function(attr) {
+      if (!arguments.length || !this._previousAttributes) return null;
+      return this._previousAttributes[attr];
+    },
+
+    // Get all of the attributes of the model at the time of the previous
+    // `"change"` event.
+    previousAttributes: function() {
+      return _.clone(this._previousAttributes);
+    },
+
+    // Check if the model is currently in a valid state. It's only possible to
+    // get into an *invalid* state if you're using silent changes.
+    isValid: function() {
+      return !this.validate(this.attributes);
+    },
+
+    // Run validation against the next complete set of model attributes,
+    // returning `true` if all is well. If a specific `error` callback has
+    // been passed, call that instead of firing the general `"error"` event.
+    _validate: function(attrs, options) {
+      if (options.silent || !this.validate) return true;
+      attrs = _.extend({}, this.attributes, attrs);
+      var error = this.validate(attrs, options);
+      if (!error) return true;
+      if (options && options.error) {
+        options.error(this, error, options);
+      } else {
+        this.trigger('error', this, error, options);
+      }
+      return false;
+    }
+
+  });
+
+  // Backbone.Collection
+  // -------------------
+
+  // Provides a standard collection class for our sets of models, ordered
+  // or unordered. If a `comparator` is specified, the Collection will maintain
+  // its models in sort order, as they're added and removed.
+  var Collection = Backbone.Collection = function(models, options) {
+    options || (options = {});
+    if (options.model) this.model = options.model;
+    if (options.comparator) this.comparator = options.comparator;
+    this._reset();
+    this.initialize.apply(this, arguments);
+    if (models) this.reset(models, {silent: true, parse: options.parse});
+  };
+
+  // Define the Collection's inheritable methods.
+  _.extend(Collection.prototype, Events, {
+
+    // The default model for a collection is just a **Backbone.Model**.
+    // This should be overridden in most cases.
+    model: Model,
+
+    // Initialize is an empty function by default. Override it with your own
+    // initialization logic.
+    initialize: function(){},
+
+    // The JSON representation of a Collection is an array of the
+    // models' attributes.
+    toJSON: function(options) {
+      return this.map(function(model){ return model.toJSON(options); });
+    },
+
+    // Add a model, or list of models to the set. Pass **silent** to avoid
+    // firing the `add` event for every new model.
+    add: function(models, options) {
+      var i, index, length, model, cid, id, cids = {}, ids = {}, dups = [];
+      options || (options = {});
+      models = _.isArray(models) ? models.slice() : [models];
+
+      // Begin by turning bare objects into model references, and preventing
+      // invalid models or duplicate models from being added.
+      for (i = 0, length = models.length; i < length; i++) {
+        if (!(model = models[i] = this._prepareModel(models[i], options))) {
+          throw new Error("Can't add an invalid model to a collection");
+        }
+        cid = model.cid;
+        id = model.id;
+        if (cids[cid] || this._byCid[cid] || ((id != null) && (ids[id] || this._byId[id]))) {
+          dups.push(i);
+          continue;
+        }
+        cids[cid] = ids[id] = model;
+      }
+
+      // Remove duplicates.
+      i = dups.length;
+      while (i--) {
+        models.splice(dups[i], 1);
+      }
+
+      // Listen to added models' events, and index models for lookup by
+      // `id` and by `cid`.
+      for (i = 0, length = models.length; i < length; i++) {
+        (model = models[i]).on('all', this._onModelEvent, this);
+        this._byCid[model.cid] = model;
+        if (model.id != null) this._byId[model.id] = model;
+      }
+
+      // Insert models into the collection, re-sorting if needed, and triggering
+      // `add` events unless silenced.
+      this.length += length;
+      index = options.at != null ? options.at : this.models.length;
+      splice.apply(this.models, [index, 0].concat(models));
+      if (this.comparator) this.sort({silent: true});
+      if (options.silent) return this;
+      for (i = 0, length = this.models.length; i < length; i++) {
+        if (!cids[(model = this.models[i]).cid]) continue;
+        options.index = i;
+        model.trigger('add', model, this, options);
+      }
+      return this;
+    },
+
+    // Remove a model, or a list of models from the set. Pass silent to avoid
+    // firing the `remove` event for every model removed.
+    remove: function(models, options) {
+      var i, l, index, model;
+      options || (options = {});
+      models = _.isArray(models) ? models.slice() : [models];
+      for (i = 0, l = models.length; i < l; i++) {
+        model = this.getByCid(models[i]) || this.get(models[i]);
+        if (!model) continue;
+        delete this._byId[model.id];
+        delete this._byCid[model.cid];
+        index = this.indexOf(model);
+        this.models.splice(index, 1);
+        this.length--;
+        if (!options.silent) {
+          options.index = index;
+          model.trigger('remove', model, this, options);
+        }
+        this._removeReference(model);
+      }
+      return this;
+    },
+
+    // Add a model to the end of the collection.
+    push: function(model, options) {
+      model = this._prepareModel(model, options);
+      this.add(model, options);
+      return model;
+    },
+
+    // Remove a model from the end of the collection.
+    pop: function(options) {
+      var model = this.at(this.length - 1);
+      this.remove(model, options);
+      return model;
+    },
+
+    // Add a model to the beginning of the collection.
+    unshift: function(model, options) {
+      model = this._prepareModel(model, options);
+      this.add(model, _.extend({at: 0}, options));
+      return model;
+    },
+
+    // Remove a model from the beginning of the collection.
+    shift: function(options) {
+      var model = this.at(0);
+      this.remove(model, options);
+      return model;
+    },
+
+    // Get a model from the set by id.
+    get: function(id) {
+      if (id == null) return void 0;
+      return this._byId[id.id != null ? id.id : id];
+    },
+
+    // Get a model from the set by client id.
+    getByCid: function(cid) {
+      return cid && this._byCid[cid.cid || cid];
+    },
+
+    // Get the model at the given index.
+    at: function(index) {
+      return this.models[index];
+    },
+
+    // Return models with matching attributes. Useful for simple cases of `filter`.
+    where: function(attrs) {
+      if (_.isEmpty(attrs)) return [];
+      return this.filter(function(model) {
+        for (var key in attrs) {
+          if (attrs[key] !== model.get(key)) return false;
+        }
+        return true;
+      });
+    },
+
+    // Force the collection to re-sort itself. You don't need to call this under
+    // normal circumstances, as the set will maintain sort order as each item
+    // is added.
+    sort: function(options) {
+      options || (options = {});
+      if (!this.comparator) throw new Error('Cannot sort a set without a comparator');
+      var boundComparator = _.bind(this.comparator, this);
+      if (this.comparator.length == 1) {
+        this.models = this.sortBy(boundComparator);
+      } else {
+        this.models.sort(boundComparator);
+      }
+      if (!options.silent) this.trigger('reset', this, options);
+      return this;
+    },
+
+    // Pluck an attribute from each model in the collection.
+    pluck: function(attr) {
+      return _.map(this.models, function(model){ return model.get(attr); });
+    },
+
+    // When you have more items than you want to add or remove individually,
+    // you can reset the entire set with a new list of models, without firing
+    // any `add` or `remove` events. Fires `reset` when finished.
+    reset: function(models, options) {
+      models  || (models = []);
+      options || (options = {});
+      for (var i = 0, l = this.models.length; i < l; i++) {
+        this._removeReference(this.models[i]);
+      }
+      this._reset();
+      this.add(models, _.extend({silent: true}, options));
+      if (!options.silent) this.trigger('reset', this, options);
+      return this;
+    },
+
+    // Fetch the default set of models for this collection, resetting the
+    // collection when they arrive. If `add: true` is passed, appends the
+    // models to the collection instead of resetting.
+    fetch: function(options) {
+      options = options ? _.clone(options) : {};
+      if (options.parse === undefined) options.parse = true;
+      var collection = this;
+      var success = options.success;
+      options.success = function(resp, status, xhr) {
+        collection[options.add ? 'add' : 'reset'](collection.parse(resp, xhr), options);
+        if (success) success(collection, resp);
+      };
+      options.error = Backbone.wrapError(options.error, collection, options);
+      return (this.sync || Backbone.sync).call(this, 'read', this, options);
+    },
+
+    // Create a new instance of a model in this collection. Add the model to the
+    // collection immediately, unless `wait: true` is passed, in which case we
+    // wait for the server to agree.
+    create: function(model, options) {
+      var coll = this;
+      options = options ? _.clone(options) : {};
+      model = this._prepareModel(model, options);
+      if (!model) return false;
+      if (!options.wait) coll.add(model, options);
+      var success = options.success;
+      options.success = function(nextModel, resp, xhr) {
+        if (options.wait) coll.add(nextModel, options);
+        if (success) {
+          success(nextModel, resp);
+        } else {
+          nextModel.trigger('sync', model, resp, options);
+        }
+      };
+      model.save(null, options);
+      return model;
+    },
+
+    // **parse** converts a response into a list of models to be added to the
+    // collection. The default implementation is just to pass it through.
+    parse: function(resp, xhr) {
+      return resp;
+    },
+
+    // Proxy to _'s chain. Can't be proxied the same way the rest of the
+    // underscore methods are proxied because it relies on the underscore
+    // constructor.
+    chain: function () {
+      return _(this.models).chain();
+    },
+
+    // Reset all internal state. Called when the collection is reset.
+    _reset: function(options) {
+      this.length = 0;
+      this.models = [];
+      this._byId  = {};
+      this._byCid = {};
+    },
+
+    // Prepare a model or hash of attributes to be added to this collection.
+    _prepareModel: function(model, options) {
+      options || (options = {});
+      if (!(model instanceof Model)) {
+        var attrs = model;
+        options.collection = this;
+        model = new this.model(attrs, options);
+        if (!model._validate(model.attributes, options)) model = false;
+      } else if (!model.collection) {
+        model.collection = this;
+      }
+      return model;
+    },
+
+    // Internal method to remove a model's ties to a collection.
+    _removeReference: function(model) {
+      if (this == model.collection) {
+        delete model.collection;
+      }
+      model.off('all', this._onModelEvent, this);
+    },
+
+    // Internal method called every time a model in the set fires an event.
+    // Sets need to update their indexes when models change ids. All other
+    // events simply proxy through. "add" and "remove" events that originate
+    // in other collections are ignored.
+    _onModelEvent: function(event, model, collection, options) {
+      if ((event == 'add' || event == 'remove') && collection != this) return;
+      if (event == 'destroy') {
+        this.remove(model, options);
+      }
+      if (model && event === 'change:' + model.idAttribute) {
+        delete this._byId[model.previous(model.idAttribute)];
+        this._byId[model.id] = model;
+      }
+      this.trigger.apply(this, arguments);
+    }
+
+  });
+
+  // Underscore methods that we want to implement on the Collection.
+  var methods = ['forEach', 'each', 'map', 'reduce', 'reduceRight', 'find',
+    'detect', 'filter', 'select', 'reject', 'every', 'all', 'some', 'any',
+    'include', 'contains', 'invoke', 'max', 'min', 'sortBy', 'sortedIndex',
+    'toArray', 'size', 'first', 'initial', 'rest', 'last', 'without', 'indexOf',
+    'shuffle', 'lastIndexOf', 'isEmpty', 'groupBy'];
+
+  // Mix in each Underscore method as a proxy to `Collection#models`.
+  _.each(methods, function(method) {
+    Collection.prototype[method] = function() {
+      return _[method].apply(_, [this.models].concat(_.toArray(arguments)));
+    };
+  });
+
+  // Backbone.Router
+  // -------------------
+
+  // Routers map faux-URLs to actions, and fire events when routes are
+  // matched. Creating a new one sets its `routes` hash, if not set statically.
+  var Router = Backbone.Router = function(options) {
+    options || (options = {});
+    if (options.routes) this.routes = options.routes;
+    this._bindRoutes();
+    this.initialize.apply(this, arguments);
+  };
+
+  // Cached regular expressions for matching named param parts and splatted
+  // parts of route strings.
+  var namedParam    = /:\w+/g;
+  var splatParam    = /\*\w+/g;
+  var escapeRegExp  = /[-[\]{}()+?.,\\^$|#\s]/g;
+
+  // Set up all inheritable **Backbone.Router** properties and methods.
+  _.extend(Router.prototype, Events, {
+
+    // Initialize is an empty function by default. Override it with your own
+    // initialization logic.
+    initialize: function(){},
+
+    // Manually bind a single named route to a callback. For example:
+    //
+    //     this.route('search/:query/p:num', 'search', function(query, num) {
+    //       ...
+    //     });
+    //
+    route: function(route, name, callback) {
+      Backbone.history || (Backbone.history = new History);
+      if (!_.isRegExp(route)) route = this._routeToRegExp(route);
+      if (!callback) callback = this[name];
+      Backbone.history.route(route, _.bind(function(fragment) {
+        var args = this._extractParameters(route, fragment);
+        callback && callback.apply(this, args);
+        this.trigger.apply(this, ['route:' + name].concat(args));
+        Backbone.history.trigger('route', this, name, args);
+      }, this));
+      return this;
+    },
+
+    // Simple proxy to `Backbone.history` to save a fragment into the history.
+    navigate: function(fragment, options) {
+      Backbone.history.navigate(fragment, options);
+    },
+
+    // Bind all defined routes to `Backbone.history`. We have to reverse the
+    // order of the routes here to support behavior where the most general
+    // routes can be defined at the bottom of the route map.
+    _bindRoutes: function() {
+      if (!this.routes) return;
+      var routes = [];
+      for (var route in this.routes) {
+        routes.unshift([route, this.routes[route]]);
+      }
+      for (var i = 0, l = routes.length; i < l; i++) {
+        this.route(routes[i][0], routes[i][1], this[routes[i][1]]);
+      }
+    },
+
+    // Convert a route string into a regular expression, suitable for matching
+    // against the current location hash.
+    _routeToRegExp: function(route) {
+      route = route.replace(escapeRegExp, '\\$&')
+                   .replace(namedParam, '([^\/]+)')
+                   .replace(splatParam, '(.*?)');
+      return new RegExp('^' + route + '$');
+    },
+
+    // Given a route, and a URL fragment that it matches, return the array of
+    // extracted parameters.
+    _extractParameters: function(route, fragment) {
+      return route.exec(fragment).slice(1);
+    }
+
+  });
+
+  // Backbone.History
+  // ----------------
+
+  // Handles cross-browser history management, based on URL fragments. If the
+  // browser does not support `onhashchange`, falls back to polling.
+  var History = Backbone.History = function() {
+    this.handlers = [];
+    _.bindAll(this, 'checkUrl');
+  };
+
+  // Cached regex for cleaning leading hashes and slashes .
+  var routeStripper = /^[#\/]/;
+
+  // Cached regex for detecting MSIE.
+  var isExplorer = /msie [\w.]+/;
+
+  // Has the history handling already been started?
+  History.started = false;
+
+  // Set up all inheritable **Backbone.History** properties and methods.
+  _.extend(History.prototype, Events, {
+
+    // The default interval to poll for hash changes, if necessary, is
+    // twenty times a second.
+    interval: 50,
+
+    // Gets the true hash value. Cannot use location.hash directly due to bug
+    // in Firefox where location.hash will always be decoded.
+    getHash: function(windowOverride) {
+      var loc = windowOverride ? windowOverride.location : window.location;
+      var match = loc.href.match(/#(.*)$/);
+      return match ? match[1] : '';
+    },
+
+    // Get the cross-browser normalized URL fragment, either from the URL,
+    // the hash, or the override.
+    getFragment: function(fragment, forcePushState) {
+      if (fragment == null) {
+        if (this._hasPushState || forcePushState) {
+          fragment = window.location.pathname;
+          var search = window.location.search;
+          if (search) fragment += search;
+        } else {
+          fragment = this.getHash();
+        }
+      }
+      if (!fragment.indexOf(this.options.root)) fragment = fragment.substr(this.options.root.length);
+      return fragment.replace(routeStripper, '');
+    },
+
+    // Start the hash change handling, returning `true` if the current URL matches
+    // an existing route, and `false` otherwise.
+    start: function(options) {
+      if (History.started) throw new Error("Backbone.history has already been started");
+      History.started = true;
+
+      // Figure out the initial configuration. Do we need an iframe?
+      // Is pushState desired ... is it available?
+      this.options          = _.extend({}, {root: '/'}, this.options, options);
+      this._wantsHashChange = this.options.hashChange !== false;
+      this._wantsPushState  = !!this.options.pushState;
+      this._hasPushState    = !!(this.options.pushState && window.history && window.history.pushState);
+      var fragment          = this.getFragment();
+      var docMode           = document.documentMode;
+      var oldIE             = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));
+
+      if (oldIE) {
+        this.iframe = $('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo('body')[0].contentWindow;
+        this.navigate(fragment);
+      }
+
+      // Depending on whether we're using pushState or hashes, and whether
+      // 'onhashchange' is supported, determine how we check the URL state.
+      if (this._hasPushState) {
+        $(window).bind('popstate', this.checkUrl);
+      } else if (this._wantsHashChange && ('onhashchange' in window) && !oldIE) {
+        $(window).bind('hashchange', this.checkUrl);
+      } else if (this._wantsHashChange) {
+        this._checkUrlInterval = setInterval(this.checkUrl, this.interval);
+      }
+
+      // Determine if we need to change the base url, for a pushState link
+      // opened by a non-pushState browser.
+      this.fragment = fragment;
+      var loc = window.location;
+      var atRoot  = loc.pathname == this.options.root;
+
+      // If we've started off with a route from a `pushState`-enabled browser,
+      // but we're currently in a browser that doesn't support it...
+      if (this._wantsHashChange && this._wantsPushState && !this._hasPushState && !atRoot) {
+        this.fragment = this.getFragment(null, true);
+        window.location.replace(this.options.root + '#' + this.fragment);
+        // Return immediately as browser will do redirect to new url
+        return true;
+
+      // Or if we've started out with a hash-based route, but we're currently
+      // in a browser where it could be `pushState`-based instead...
+      } else if (this._wantsPushState && this._hasPushState && atRoot && loc.hash) {
+        this.fragment = this.getHash().replace(routeStripper, '');
+        window.history.replaceState({}, document.title, loc.protocol + '//' + loc.host + this.options.root + this.fragment);
+      }
+
+      if (!this.options.silent) {
+        return this.loadUrl();
+      }
+    },
+
+    // Disable Backbone.history, perhaps temporarily. Not useful in a real app,
+    // but possibly useful for unit testing Routers.
+    stop: function() {
+      $(window).unbind('popstate', this.checkUrl).unbind('hashchange', this.checkUrl);
+      clearInterval(this._checkUrlInterval);
+      History.started = false;
+    },
+
+    // Add a route to be tested when the fragment changes. Routes added later
+    // may override previous routes.
+    route: function(route, callback) {
+      this.handlers.unshift({route: route, callback: callback});
+    },
+
+    // Checks the current URL to see if it has changed, and if it has,
+    // calls `loadUrl`, normalizing across the hidden iframe.
+    checkUrl: function(e) {
+      var current = this.getFragment();
+      if (current == this.fragment && this.iframe) current = this.getFragment(this.getHash(this.iframe));
+      if (current == this.fragment) return false;
+      if (this.iframe) this.navigate(current);
+      this.loadUrl() || this.loadUrl(this.getHash());
+    },
+
+    // Attempt to load the current URL fragment. If a route succeeds with a
+    // match, returns `true`. If no defined routes matches the fragment,
+    // returns `false`.
+    loadUrl: function(fragmentOverride) {
+      var fragment = this.fragment = this.getFragment(fragmentOverride);
+      var matched = _.any(this.handlers, function(handler) {
+        if (handler.route.test(fragment)) {
+          handler.callback(fragment);
+          return true;
+        }
+      });
+      return matched;
+    },
+
+    // Save a fragment into the hash history, or replace the URL state if the
+    // 'replace' option is passed. You are responsible for properly URL-encoding
+    // the fragment in advance.
+    //
+    // The options object can contain `trigger: true` if you wish to have the
+    // route callback be fired (not usually desirable), or `replace: true`, if
+    // you wish to modify the current URL without adding an entry to the history.
+    navigate: function(fragment, options) {
+      if (!History.started) return false;
+      if (!options || options === true) options = {trigger: options};
+      var frag = (fragment || '').replace(routeStripper, '');
+      if (this.fragment == frag) return;
+
+      // If pushState is available, we use it to set the fragment as a real URL.
+      if (this._hasPushState) {
+        if (frag.indexOf(this.options.root) != 0) frag = this.options.root + frag;
+        this.fragment = frag;
+        window.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, frag);
+
+      // If hash changes haven't been explicitly disabled, update the hash
+      // fragment to store history.
+      } else if (this._wantsHashChange) {
+        this.fragment = frag;
+        this._updateHash(window.location, frag, options.replace);
+        if (this.iframe && (frag != this.getFragment(this.getHash(this.iframe)))) {
+          // Opening and closing the iframe tricks IE7 and earlier to push a history entry on hash-tag change.
+          // When replace is true, we don't want this.
+          if(!options.replace) this.iframe.document.open().close();
+          this._updateHash(this.iframe.location, frag, options.replace);
+        }
+
+      // If you've told us that you explicitly don't want fallback hashchange-
+      // based history, then `navigate` becomes a page refresh.
+      } else {
+        window.location.assign(this.options.root + fragment);
+      }
+      if (options.trigger) this.loadUrl(fragment);
+    },
+
+    // Update the hash location, either replacing the current entry, or adding
+    // a new one to the browser history.
+    _updateHash: function(location, fragment, replace) {
+      if (replace) {
+        location.replace(location.toString().replace(/(javascript:|#).*$/, '') + '#' + fragment);
+      } else {
+        location.hash = fragment;
+      }
+    }
+  });
+
+  // Backbone.View
+  // -------------
+
+  // Creating a Backbone.View creates its initial element outside of the DOM,
+  // if an existing element is not provided...
+  var View = Backbone.View = function(options) {
+    this.cid = _.uniqueId('view');
+    this._configure(options || {});
+    this._ensureElement();
+    this.initialize.apply(this, arguments);
+    this.delegateEvents();
+  };
+
+  // Cached regex to split keys for `delegate`.
+  var delegateEventSplitter = /^(\S+)\s*(.*)$/;
+
+  // List of view options to be merged as properties.
+  var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName'];
+
+  // Set up all inheritable **Backbone.View** properties and methods.
+  _.extend(View.prototype, Events, {
+
+    // The default `tagName` of a View's element is `"div"`.
+    tagName: 'div',
+
+    // jQuery delegate for element lookup, scoped to DOM elements within the
+    // current view. This should be prefered to global lookups where possible.
+    $: function(selector) {
+      return this.$el.find(selector);
+    },
+
+    // Initialize is an empty function by default. Override it with your own
+    // initialization logic.
+    initialize: function(){},
+
+    // **render** is the core function that your view should override, in order
+    // to populate its element (`this.el`), with the appropriate HTML. The
+    // convention is for **render** to always return `this`.
+    render: function() {
+      return this;
+    },
+
+    // Remove this view from the DOM. Note that the view isn't present in the
+    // DOM by default, so calling this method may be a no-op.
+    remove: function() {
+      this.$el.remove();
+      return this;
+    },
+
+    // For small amounts of DOM Elements, where a full-blown template isn't
+    // needed, use **make** to manufacture elements, one at a time.
+    //
+    //     var el = this.make('li', {'class': 'row'}, this.model.escape('title'));
+    //
+    make: function(tagName, attributes, content) {
+      var el = document.createElement(tagName);
+      if (attributes) $(el).attr(attributes);
+      if (content) $(el).html(content);
+      return el;
+    },
+
+    // Change the view's element (`this.el` property), including event
+    // re-delegation.
+    setElement: function(element, delegate) {
+      if (this.$el) this.undelegateEvents();
+      this.$el = (element instanceof $) ? element : $(element);
+      this.el = this.$el[0];
+      if (delegate !== false) this.delegateEvents();
+      return this;
+    },
+
+    // Set callbacks, where `this.events` is a hash of
+    //
+    // *{"event selector": "callback"}*
+    //
+    //     {
+    //       'mousedown .title':  'edit',
+    //       'click .button':     'save'
+    //       'click .open':       function(e) { ... }
+    //     }
+    //
+    // pairs. Callbacks will be bound to the view, with `this` set properly.
+    // Uses event delegation for efficiency.
+    // Omitting the selector binds the event to `this.el`.
+    // This only works for delegate-able events: not `focus`, `blur`, and
+    // not `change`, `submit`, and `reset` in Internet Explorer.
+    delegateEvents: function(events) {
+      if (!(events || (events = getValue(this, 'events')))) return;
+      this.undelegateEvents();
+      for (var key in events) {
+        var method = events[key];
+        if (!_.isFunction(method)) method = this[events[key]];
+        if (!method) throw new Error('Method "' + events[key] + '" does not exist');
+        var match = key.match(delegateEventSplitter);
+        var eventName = match[1], selector = match[2];
+        method = _.bind(method, this);
+        eventName += '.delegateEvents' + this.cid;
+        if (selector === '') {
+          this.$el.bind(eventName, method);
+        } else {
+          this.$el.delegate(selector, eventName, method);
+        }
+      }
+    },
+
+    // Clears all callbacks previously bound to the view with `delegateEvents`.
+    // You usually don't need to use this, but may wish to if you have multiple
+    // Backbone views attached to the same DOM element.
+    undelegateEvents: function() {
+      this.$el.unbind('.delegateEvents' + this.cid);
+    },
+
+    // Performs the initial configuration of a View with a set of options.
+    // Keys with special meaning *(model, collection, id, className)*, are
+    // attached directly to the view.
+    _configure: function(options) {
+      if (this.options) options = _.extend({}, this.options, options);
+      for (var i = 0, l = viewOptions.length; i < l; i++) {
+        var attr = viewOptions[i];
+        if (options[attr]) this[attr] = options[attr];
+      }
+      this.options = options;
+    },
+
+    // Ensure that the View has a DOM element to render into.
+    // If `this.el` is a string, pass it through `$()`, take the first
+    // matching element, and re-assign it to `el`. Otherwise, create
+    // an element from the `id`, `className` and `tagName` properties.
+    _ensureElement: function() {
+      if (!this.el) {
+        var attrs = getValue(this, 'attributes') || {};
+        if (this.id) attrs.id = this.id;
+        if (this.className) attrs['class'] = this.className;
+        this.setElement(this.make(this.tagName, attrs), false);
+      } else {
+        this.setElement(this.el, false);
+      }
+    }
+
+  });
+
+  // The self-propagating extend function that Backbone classes use.
+  var extend = function (protoProps, classProps) {
+    var child = inherits(this, protoProps, classProps);
+    child.extend = this.extend;
+    return child;
+  };
+
+  // Set up inheritance for the model, collection, and view.
+  Model.extend = Collection.extend = Router.extend = View.extend = extend;
+
+  // Backbone.sync
+  // -------------
+
+  // Map from CRUD to HTTP for our default `Backbone.sync` implementation.
+  var methodMap = {
+    'create': 'POST',
+    'update': 'PUT',
+    'delete': 'DELETE',
+    'read':   'GET'
+  };
+
+  // Override this function to change the manner in which Backbone persists
+  // models to the server. You will be passed the type of request, and the
+  // model in question. By default, makes a RESTful Ajax request
+  // to the model's `url()`. Some possible customizations could be:
+  //
+  // * Use `setTimeout` to batch rapid-fire updates into a single request.
+  // * Send up the models as XML instead of JSON.
+  // * Persist models via WebSockets instead of Ajax.
+  //
+  // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
+  // as `POST`, with a `_method` parameter containing the true HTTP method,
+  // as well as all requests with the body as `application/x-www-form-urlencoded`
+  // instead of `application/json` with the model in a param named `model`.
+  // Useful when interfacing with server-side languages like **PHP** that make
+  // it difficult to read the body of `PUT` requests.
+  Backbone.sync = function(method, model, options) {
+    var type = methodMap[method];
+
+    // Default options, unless specified.
+    options || (options = {});
+
+    // Default JSON-request options.
+    var params = {type: type, dataType: 'json'};
+
+    // Ensure that we have a URL.
+    if (!options.url) {
+      params.url = getValue(model, 'url') || urlError();
+    }
+
+    // Ensure that we have the appropriate request data.
+    if (!options.data && model && (method == 'create' || method == 'update')) {
+      params.contentType = 'application/json';
+      params.data = JSON.stringify(model.toJSON());
+    }
+
+    // For older servers, emulate JSON by encoding the request into an HTML-form.
+    if (Backbone.emulateJSON) {
+      params.contentType = 'application/x-www-form-urlencoded';
+      params.data = params.data ? {model: params.data} : {};
+    }
+
+    // For older servers, emulate HTTP by mimicking the HTTP method with `_method`
+    // And an `X-HTTP-Method-Override` header.
+    if (Backbone.emulateHTTP) {
+      if (type === 'PUT' || type === 'DELETE') {
+        if (Backbone.emulateJSON) params.data._method = type;
+        params.type = 'POST';
+        params.beforeSend = function(xhr) {
+          xhr.setRequestHeader('X-HTTP-Method-Override', type);
+        };
+      }
+    }
+
+    // Don't process data on a non-GET request.
+    if (params.type !== 'GET' && !Backbone.emulateJSON) {
+      params.processData = false;
+    }
+
+    // Make the request, allowing the user to override any Ajax options.
+    return $.ajax(_.extend(params, options));
+  };
+
+  // Wrap an optional error callback with a fallback error event.
+  Backbone.wrapError = function(onError, originalModel, options) {
+    return function(model, resp) {
+      resp = model === originalModel ? resp : model;
+      if (onError) {
+        onError(originalModel, resp, options);
+      } else {
+        originalModel.trigger('error', originalModel, resp, options);
+      }
+    };
+  };
+
+  // Helpers
+  // -------
+
+  // Shared empty constructor function to aid in prototype-chain creation.
+  var ctor = function(){};
+
+  // Helper function to correctly set up the prototype chain, for subclasses.
+  // Similar to `goog.inherits`, but uses a hash of prototype properties and
+  // class properties to be extended.
+  var inherits = function(parent, protoProps, staticProps) {
+    var child;
+
+    // The constructor function for the new subclass is either defined by you
+    // (the "constructor" property in your `extend` definition), or defaulted
+    // by us to simply call the parent's constructor.
+    if (protoProps && protoProps.hasOwnProperty('constructor')) {
+      child = protoProps.constructor;
+    } else {
+      child = function(){ parent.apply(this, arguments); };
+    }
+
+    // Inherit class (static) properties from parent.
+    _.extend(child, parent);
+
+    // Set the prototype chain to inherit from `parent`, without calling
+    // `parent`'s constructor function.
+    ctor.prototype = parent.prototype;
+    child.prototype = new ctor();
+
+    // Add prototype properties (instance properties) to the subclass,
+    // if supplied.
+    if (protoProps) _.extend(child.prototype, protoProps);
+
+    // Add static properties to the constructor function, if supplied.
+    if (staticProps) _.extend(child, staticProps);
+
+    // Correctly set child's `prototype.constructor`.
+    child.prototype.constructor = child;
+
+    // Set a convenience property in case the parent's prototype is needed later.
+    child.__super__ = parent.prototype;
+
+    return child;
+  };
+
+  // Helper function to get a value from a Backbone object as a property
+  // or as a function.
+  var getValue = function(object, prop) {
+    if (!(object && object[prop])) return null;
+    return _.isFunction(object[prop]) ? object[prop]() : object[prop];
+  };
+
+  // Throw an error when a URL is needed, and none is supplied.
+  var urlError = function() {
+    throw new Error('A "url" property or function must be specified');
+  };
+
+}).call(this);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/metadataplayer/libs/jquery.mousewheel.min.js	Thu Sep 27 12:23:41 2012 +0200
@@ -0,0 +1,12 @@
+/*! Copyright (c) 2011 Brandon Aaron (http://brandonaaron.net)
+ * Licensed under the MIT License (LICENSE.txt).
+ *
+ * Thanks to: http://adomas.org/javascript-mouse-wheel/ for some pointers.
+ * Thanks to: Mathias Bank(http://www.mathias-bank.de) for a scope bug fix.
+ * Thanks to: Seamus Leahy for adding deltaX and deltaY
+ *
+ * Version: 3.0.6
+ * 
+ * Requires: 1.2.2+
+ */
+(function(a){function d(b){var c=b||window.event,d=[].slice.call(arguments,1),e=0,f=!0,g=0,h=0;return b=a.event.fix(c),b.type="mousewheel",c.wheelDelta&&(e=c.wheelDelta/120),c.detail&&(e=-c.detail/3),h=e,c.axis!==undefined&&c.axis===c.HORIZONTAL_AXIS&&(h=0,g=-1*e),c.wheelDeltaY!==undefined&&(h=c.wheelDeltaY/120),c.wheelDeltaX!==undefined&&(g=-1*c.wheelDeltaX/120),d.unshift(b,e,g,h),(a.event.dispatch||a.event.handle).apply(this,d)}var b=["DOMMouseScroll","mousewheel"];if(a.event.fixHooks)for(var c=b.length;c;)a.event.fixHooks[b[--c]]=a.event.mouseHooks;a.event.special.mousewheel={setup:function(){if(this.addEventListener)for(var a=b.length;a;)this.addEventListener(b[--a],d,!1);else this.onmousewheel=d},teardown:function(){if(this.removeEventListener)for(var a=b.length;a;)this.removeEventListener(b[--a],d,!1);else this.onmousewheel=null}},a.fn.extend({mousewheel:function(a){return a?this.bind("mousewheel",a):this.trigger("mousewheel")},unmousewheel:function(a){return this.unbind("mousewheel",a)}})})(jQuery)
Binary file metadataplayer/libs/paper.js has changed
Binary file metadataplayer/libs/player.swf has changed
Binary file metadataplayer/libs/record_mic.swf has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/metadataplayer/libs/renkan-publish.js	Thu Sep 27 12:23:41 2012 +0200
@@ -0,0 +1,1079 @@
+/* 
+ *  Copyright 2012 Institut de recherche et d'innovation 
+ *  contributor(s) : Yves-Marie Haussonne, Raphael Velt, Samuel Huron
+ *   
+ *  contact@iri.centrepompidou.fr
+ *  http://www.iri.centrepompidou.fr 
+ *   
+ *  This software is a computer program whose purpose is to show and add annotations on a video .
+ *  This software is governed by the CeCILL-C license under French law and
+ *  abiding by the rules of distribution of free software. You can  use, 
+ *  modify and/ or redistribute the software under the terms of the CeCILL-C
+ *  license as circulated by CEA, CNRS and INRIA at the following URL
+ *  "http://www.cecill.info". 
+ *  
+ *  The fact that you are presently reading this means that you have had
+ *  knowledge of the CeCILL-C license and that you accept its terms.
+*/
+
+if (typeof Rkns !== "object") {
+    Rkns = {}
+}
+
+Rkns.$ = jQuery;
+
+Rkns._ = _;
+
+Rkns.i18n = {
+    en: {
+        zoom_in: "Zoom In",
+        zoom_out: "Zoom Out",
+        see_in_project: 'See also <b>"{node}"</b> in <b>"{project}"</b>'
+    }
+}
+
+Rkns.Utils = {
+    inherit : function(_baseClass) {
+        var _class = function() {
+            _baseClass.apply(this, Array.prototype.slice.call(arguments, 0));
+            if (typeof this._init == "function") {
+                this._init.apply(this, Array.prototype.slice.call(arguments, 0));
+            }
+        }
+        _class.prototype = new _baseClass();
+        return _class;
+    }
+}
+
+Rkns.Models = {};
+
+Rkns.Models.getUID = function(obj) {
+    var guid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
+        var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
+        return v.toString(16);
+    });
+    return obj.type + "-" + guid; 
+};
+
+Rkns.Models.RenkanModel = Backbone.RelationalModel.extend({
+    idAttribute : "_id",
+    constructor: function(options) {
+
+        if (typeof options !== "undefined") {
+            options._id = options._id || options.id || Rkns.Models.getUID(this);
+            options.title = options.title || "(untitled " + this.type + ")";
+            options.description = options.description || "";
+            options.uri = options.uri || "";
+
+            if(typeof this.prepare === "function") {
+                options = this.prepare(options);
+            }
+        }
+        Backbone.RelationalModel.prototype.constructor.call(this, options);
+    },
+    validate: function() {
+        if(!this.type) {
+            return "object has no type";
+        }
+    },
+    addReference : function(_options, _propName, _list, _id, _default) {
+        var _element = _list.get(_id);
+        if (typeof _element === "undefined" && typeof _default !== "undefined") {
+            _options[_propName ] = _default;
+        }
+        else {
+            _options[_propName ] = _element;
+        }
+    }
+});
+    
+// USER
+Rkns.Models.User = Rkns.Models.RenkanModel.extend({
+    type: "user",
+    prepare: function(options) {
+        options.color = options.color || "#666666";
+        return options;
+    },
+    toJSON: function() {
+        return {
+            id: this.get("_id"),
+            title: this.get("title"),
+            uri: this.get("uri"),
+            description: this.get("description"),
+            color: this.get("color"),
+        }
+    },
+});
+
+// NODE
+Rkns.Models.Node = Rkns.Models.RenkanModel.extend({
+    type: "node",
+    relations: [{
+        type: Backbone.HasOne,
+        key: "created_by",
+        relatedModel: Rkns.Models.User
+    }],
+    prepare: function(options) {
+        project = options.project;
+        this.addReference(options, "created_by", project.get("users"), options.created_by, project.current_user);
+        options.description = options.description || "";
+        return options;
+    },
+    toJSON: function() {
+        return {
+            id: this.get("_id"),
+            title: this.get("title"),
+            uri: this.get("uri"),
+            description: this.get("description"),
+            position: this.get("position"),
+            image: this.get("image"),
+            created_by: this.get("created_by").get("_id")
+        }
+    },
+});
+
+// EDGE
+Rkns.Models.Edge = Rkns.Models.RenkanModel.extend({
+    type: "edge",
+    relations: [
+      {
+        type: Backbone.HasOne,
+        key: "created_by",
+        relatedModel: Rkns.Models.User
+      },
+      {
+        type: Backbone.HasOne,
+        key: "from",
+        relatedModel: Rkns.Models.Node
+      },
+      {
+        type: Backbone.HasOne,
+        key: "to",
+        relatedModel: Rkns.Models.Node
+      },
+    ],
+    prepare: function(options) {
+        project = options.project;
+        this.addReference(options, "created_by", project.get("users"), options.created_by, project.current_user);
+        this.addReference(options, "from", project.get("nodes"), options.from);
+        this.addReference(options, "to", project.get("nodes"), options.to);
+        return options;
+    },
+    toJSON: function() {
+        return {
+            id: this.get("_id"),
+            title: this.get("title"),
+            uri: this.get("uri"),
+            description: this.get("description"),
+            from: this.get("from").get("_id"),
+            to: this.get("to").get("_id"),
+            created_by: this.get("created_by").get("_id"),
+        }
+    },
+});
+    
+// PROJECT
+Rkns.Models.Project = Rkns.Models.RenkanModel.extend({
+    type: "project",
+    relations: [
+      {
+        type: Backbone.HasMany,
+        key: "users",
+        relatedModel: Rkns.Models.User,
+        reverseRelation: {
+            key: 'project',
+            includeInJSON: '_id'
+        },
+      },
+      {
+        type: Backbone.HasMany,
+        key: "nodes",
+        relatedModel: Rkns.Models.Node,
+        reverseRelation: {
+            key: 'project',
+            includeInJSON: '_id'
+        },
+      },
+      {
+        type: Backbone.HasMany,
+        key: "edges",
+        relatedModel: Rkns.Models.Edge,
+        reverseRelation: {
+            key: 'project',
+            includeInJSON: '_id'
+        },
+      }
+    ],
+    addUser: function(_props) {
+        _props.project = this;
+        var _user = new Rkns.Models.User(_props);
+        this.get("users").push(_user);
+        return _user;
+    },
+    addNode: function(_props) {
+        _props.project = this;
+        var _node = new Rkns.Models.Node(_props);
+        this.get("nodes").push(_node);
+        return _node;
+    },
+    addEdge: function(_props) {
+        _props.project = this;
+        var _edge = new Rkns.Models.Edge(_props);
+        this.get("edges").push(_edge);
+        return _edge;
+    },
+    removeNode: function(_model) {
+        this.get("nodes").remove(_model);
+    },
+    removeEdge: function(_model) {
+        this.get("edges").remove(_model);
+    },
+    validate: function(options) {
+        var _project = this;
+        Rkns._(options.users).each(function(_item) {
+            _item.project = _project;
+        });
+        Rkns._(options.nodes).each(function(_item) {
+            _item.project = _project;
+        });
+        Rkns._(options.edges).each(function(_item) {
+            _item.project = _project;
+        });
+    },
+    // Add event handler to remove edges when a node is removed
+    initialize: function() {
+        var _this = this;
+        this.on("remove:nodes", function(_node) {
+            _this.get("edges").remove(
+                _this.get("edges").filter(function(_edge) {
+                    return _edge.get("from") == _node || _edge.get("to") == _node;
+                })
+            );
+        });
+    }
+});
+
+/* Point of entry */
+
+Rkns.Renkan = function(_opts) {
+    if (typeof _opts.language !== "string" || typeof Rkns.i18n[_opts.language] == "undefined") {
+        _opts.language = "en";
+    }
+    if (typeof _opts.container !== "string") {
+        _opts.container = "renkan";
+    }
+    if (typeof _opts.search !== "object" || !_opts.search) {
+        _opts.search = [];
+    }
+    this.projects = [];
+    this.l10n = Rkns.i18n[_opts.language];
+    this.$ = Rkns.$("#" + _opts.container);
+    this.$.html(this.template());
+    this.uris = {};
+    this.active_project = null;
+    this.renderer = null;
+}
+
+Rkns.Renkan.prototype.template = Rkns._.template(
+    '<div class="Rk-Render"></div><ul class="Rk-Project-List"></ul>'
+);
+
+Rkns.Renkan.prototype.addProject = function(_opts) {
+    var _proj = new Rkns.Models.Project(),
+        _li = Rkns.$("<li>").addClass("Rk-Project").text("Untitled #" + (1+this.projects.length));
+    this.$.find(".Rk-Project-List").append(_li);
+    Rkns.loadJson(_proj, _opts);
+    var _this = this;
+    _li.click(function() {
+        _this.renderProject(_proj);
+    });
+    _proj.on("change:title", function() {
+        _li.html(_proj.get("title"));
+    });
+    _proj.on("select", function() {
+        _this.$.find(".Rk-Project").removeClass("active");
+        _li.addClass("active");
+    });
+    _proj.on("add:nodes", function(_node) {
+        var _uri = _node.get("uri");
+        if (_uri) {
+            if (typeof _this.uris[_uri] === "undefined") {
+                _this.uris[_uri] = [];
+            }
+            _this.uris[_uri].push(_node);
+        }
+    });
+    this.projects.push(_proj);
+    return _proj;
+}
+
+Rkns.Renkan.prototype.renderProject = function(_project) {
+    if (_project) {
+        if (this.renderer) {
+            this.renderer.destroy();
+        }
+        this.active_project = _project;
+        this.renderer = new Rkns.Renderer.Scene(this, _project);
+        this.renderer.autoScale();
+        _project.trigger("select");
+    }
+}
+
+Rkns.Renkan.prototype.renderProjectAt = function(_index) {
+    this.renderProject(this.projects[_index]);
+}
+
+Rkns.loadJson = function(_proj, _opts) {
+    if (typeof _opts.http_method == "undefined") {
+        _opts.http_method = 'PUT';
+    }
+    var _load = function() {
+        Rkns.$.getJSON(_opts.url, function(_data) {
+            _proj.set(_data);
+            if (typeof _opts.callback === "function") {
+                _opts.callback(_proj);
+            }
+        });
+    }
+    _load();
+}
+
+Rkns.Renderer = {
+    _MARGIN_X: 80,
+    _MARGIN_Y: 50,
+    _MIN_DRAG_DISTANCE: 2,
+    _NODE_RADIUS: 20,
+    _NODE_FONT_SIZE: 10,
+    _EDGE_FONT_SIZE: 9,
+    _NODE_MAX_CHAR: 30,
+    _EDGE_MAX_CHAR: 20,
+    _ARROW_LENGTH: 16,
+    _ARROW_WIDTH: 8,
+    _TOOLTIP_ARROW_LENGTH : 15,
+    _TOOLTIP_ARROW_WIDTH : 26,
+    _TOOLTIP_MARGIN : 10,
+    _TOOLTIP_PADDING : 8,
+    _TOOLTIP_GRADIENT : new paper.Gradient(['#f0f0f0', '#d0d0d0'])
+}
+
+Rkns.Renderer.Utils = {
+    shortenText : function(_text,_length) {
+        var _rgxp = new RegExp('^(.{' + _length + '}).+$');
+        return _text.replace(/(\n|\r|\r\n)/mg,' ').replace(_rgxp,'$1…');
+    },
+    drawTooltip : function(_coords, _path, _width, _xmargin, _selector) {
+        _selector.css({
+            width: (_width - 2* Rkns.Renderer._TOOLTIP_PADDING),
+        });
+        var _height = _selector.outerHeight() + 2* Rkns.Renderer._TOOLTIP_PADDING,
+            _isLeft = (_coords.x < paper.view.center.x ? 1 : -1),
+            _left = _coords.x + _isLeft * ( _xmargin + Rkns.Renderer._TOOLTIP_ARROW_LENGTH ),
+            _right = _coords.x + _isLeft * ( _xmargin + Rkns.Renderer._TOOLTIP_ARROW_LENGTH + _width ),
+            _top = _coords.y - _height / 2;
+        if (_top < Rkns.Renderer._TOOLTIP_MARGIN) {
+            _top = Math.min( Rkns.Renderer._TOOLTIP_MARGIN, _coords.y - Rkns.Renderer._TOOLTIP_ARROW_WIDTH / 2 );
+        }
+        var _bottom = _top + _height;
+        if (_bottom > (paper.view.size.height - Rkns.Renderer._TOOLTIP_MARGIN)) {
+            _bottom = Math.max( paper.view.size.height - Rkns.Renderer._TOOLTIP_MARGIN, _coords.y + Rkns.Renderer._TOOLTIP_ARROW_WIDTH / 2 );
+            _top = _bottom - _height;
+        }
+        _path.segments[0].point
+            = _path.segments[7].point
+            = _coords.add([_isLeft * _xmargin, 0]);
+        _path.segments[1].point.x
+            = _path.segments[2].point.x
+            = _path.segments[5].point.x
+            = _path.segments[6].point.x
+            = _left;
+        _path.segments[3].point.x
+            = _path.segments[4].point.x
+            = _right;
+        _path.segments[2].point.y
+            = _path.segments[3].point.y
+            = _top;
+        _path.segments[4].point.y
+            = _path.segments[5].point.y
+            = _bottom;
+        _path.segments[1].point.y = _coords.y - Rkns.Renderer._TOOLTIP_ARROW_WIDTH / 2;
+        _path.segments[6].point.y = _coords.y + Rkns.Renderer._TOOLTIP_ARROW_WIDTH / 2;
+        _path.closed = true;
+        _path.fillColor = new paper.GradientColor(Rkns.Renderer._TOOLTIP_GRADIENT, [0,_top], [0, _bottom]);
+        _selector.css({
+            left: (Rkns.Renderer._TOOLTIP_PADDING + Math.min(_left, _right)),
+            top: (Rkns.Renderer._TOOLTIP_PADDING + _top)
+        });
+    }
+}
+
+Rkns.Renderer._BaseRepresentation = function(_renderer, _model) {
+    if (typeof _renderer !== "undefined") {
+        this.renderer = _renderer;
+        this.project = _renderer.project;
+        this.model = _model;
+        if (this.model) {
+            var _this = this;
+            this._selectBinding = function() {
+                _this.select();
+            };
+            this._unselectBinding = function() {
+                _this.unselect();
+            }
+            this._changeBinding = function() {
+                _this.redraw();
+            }
+            this._removeBinding = function() {
+                _renderer.removeRepresentation(_this);
+                _renderer.redraw();
+            }
+            this.model.on("change", this._changeBinding );
+            this.model.on("remove", this._removeBinding );
+            this.model.on("select", this._selectBinding );
+            this.model.on("unselect", this._unselectBinding );
+        }
+    }
+}
+
+Rkns.Renderer._BaseRepresentation.prototype.super = function(_func) {
+    Rkns.Renderer._BaseRepresentation.prototype[_func].apply(this, Array.prototype.slice.call(arguments, 1));
+}
+
+Rkns.Renderer._BaseRepresentation.prototype.select = function() {}
+
+Rkns.Renderer._BaseRepresentation.prototype.unselect = function() {}
+
+Rkns.Renderer._BaseRepresentation.prototype.mouseup = function() {}
+
+Rkns.Renderer._BaseRepresentation.prototype.destroy = function() {
+    if (this.model) {
+        this.model.off("change", this._changeBinding );
+        this.model.off("remove", this._removeBinding );
+        this.model.off("select", this._selectBinding);
+        this.model.off("unselect", this._unselectBinding);
+    }
+}
+
+Rkns.Renderer.Node = Rkns.Utils.inherit(Rkns.Renderer._BaseRepresentation);
+
+Rkns.Renderer.Node.prototype._init = function() {
+    this.renderer.node_layer.activate();
+    this.type = "Node";
+    this.circle = new paper.Path.Circle([0, 0], Rkns.Renderer._NODE_RADIUS);
+    this.circle.fillColor = '#ffffff';
+    this.circle.__representation = this;
+    this.title = new paper.PointText([0,0]);
+    this.title.characterStyle = {
+        fontSize: Rkns.Renderer._NODE_FONT_SIZE,
+        fillColor: 'black'
+    };
+    this.title.paragraphStyle.justification = 'center';
+    this.title.__representation = this;
+    this.model_coords = new paper.Point(this.model.get("position"));
+}
+
+Rkns.Renderer.Node.prototype.redraw = function() {
+    this.paper_coords = this.renderer.toPaperCoords(this.model_coords);
+    this.circle.position = this.paper_coords;
+    this.title.content = Rkns.Renderer.Utils.shortenText(this.model.get("title"), Rkns.Renderer._NODE_MAX_CHAR);
+    this.title.position = this.paper_coords.add([0, 2 * Rkns.Renderer._NODE_RADIUS]);
+    this.circle.strokeColor = this.model.get("created_by").get("color");
+    var _img = this.model.get("image");
+    if (_img && _img !== this.img) {
+        var _image = new Image(),
+            _this = this;
+        _image.onload = function() {
+            if (_this.node_image) {
+                _this.node_image.remove();
+            }
+            _this.renderer.node_layer.activate();
+            var _ratio = Math.min(1, 2 * Rkns.Renderer._NODE_RADIUS / _image.width, 2 * Rkns.Renderer._NODE_RADIUS / _image.height );
+            var _raster = new paper.Raster(_image);
+            var _clip = new paper.Path.Circle([0, 0], Rkns.Renderer._NODE_RADIUS);
+            _raster.scale(_ratio);
+            _this.node_image = new paper.Group(_clip, _raster);
+            _this.node_image.opacity = _this.selected ? .5 : .9;
+            /* This is a workaround to allow clipping at group level */
+            _this.node_image.clipped = true;
+            _this.node_image.position = _this.paper_coords;
+            _this.node_image.__representation = _this;
+            paper.view.draw();
+        }
+        _image.src = _img;
+    }
+    this.img = _img;
+    if (this.node_image) {
+        if (!this.img) {
+            this.node_image.remove();
+            delete this.node_image;
+        } else {
+            this.node_image.position = this.paper_coords;
+        }
+    }
+}
+
+Rkns.Renderer.Node.prototype.paperShift = function(_delta) {
+    this.paper_coords = this.paper_coords.add(_delta);
+    this.model_coords = this.renderer.toModelCoords(this.paper_coords);
+    this.renderer.redraw();
+}
+
+Rkns.Renderer.Node.prototype.openTooltip = function() {
+    this.renderer.removeRepresentationsOfType("tooltip");
+    var _tooltip = this.renderer.addRepresentation("NodeTooltip",null);
+    _tooltip.node_representation = this;
+    _tooltip.draw();
+}
+
+Rkns.Renderer.Node.prototype.select = function() {
+    this.selected = true;
+    this.circle.strokeWidth = 3;
+    this.openTooltip();
+    this.circle.fillColor = "#ffff80";
+    if (this.node_image) {
+        this.node_image.opacity = .5;
+    }
+    paper.view.draw();
+}
+
+Rkns.Renderer.Node.prototype.unselect = function() {
+    this.selected = false;
+    this.circle.strokeWidth = 1;
+    this.circle.fillColor = "#ffffff";
+    if (this.node_image) {
+        this.node_image.opacity = .9;
+    }
+    paper.view.draw();
+}
+
+Rkns.Renderer.Node.prototype.mouseup = function(_event) {
+}
+
+Rkns.Renderer.Node.prototype.destroy = function(_event) {
+    this.super("destroy");
+    this.circle.remove();
+    this.title.remove();
+    if (this.node_image) {
+        this.node_image.remove();
+    }
+}
+
+/* */
+
+Rkns.Renderer.Edge = Rkns.Utils.inherit(Rkns.Renderer._BaseRepresentation);
+
+Rkns.Renderer.Edge.prototype._init = function() {
+    this.renderer.edge_layer.activate();
+    this.type = "Edge";
+    this.from_representation = this.renderer.getRepresentationByModel(this.model.get("from"));
+    this.to_representation = this.renderer.getRepresentationByModel(this.model.get("to"));
+    this.bundle = this.renderer.addToBundles(this);
+    this.line = new paper.Path();
+    this.line.add([0,0],[0,0],[0,0]);
+    this.line.__representation = this;
+    this.arrow = new paper.Path();
+    this.arrow.add([0,0],[Rkns.Renderer._ARROW_LENGTH,Rkns.Renderer._ARROW_WIDTH / 2],[0,Rkns.Renderer._ARROW_WIDTH]);
+    this.arrow.__representation = this;
+    this.text = new paper.PointText();
+    this.text.characterStyle = {
+        fontSize: Rkns.Renderer._EDGE_FONT_SIZE,
+        fillColor: 'black'
+    };
+    this.text.paragraphStyle.justification = 'center';
+    this.text.__representation = this;
+    this.text_angle = 0;
+    this.arrow_angle = 0;
+}
+
+Rkns.Renderer.Edge.prototype.redraw = function() {
+    var _p0a = this.from_representation.paper_coords,
+        _p1a = this.to_representation.paper_coords,
+        _v = _p1a.subtract(_p0a),
+        _r = _v.length,
+        _u = _v.divide(_r),
+        _group_pos = this.bundle.getPosition(this),
+        _delta = new paper.Point([- _u.y, _u.x]).multiply( 12 * _group_pos ),
+        _p0b = _p0a.add(_delta), /* Adding a 4 px difference */
+        _p1b = _p1a.add(_delta), /* to differentiate inbound and outbound links */
+        _a = _v.angle,
+        _handle = _v.divide(3),
+        _color = this.model.get("created_by").get("color");
+    this.paper_coords = _p0b.add(_p1b).divide(2);
+    this.line.strokeColor = _color;
+    this.line.segments[0].point = _p0a;
+    this.line.segments[1].point = this.paper_coords;
+    this.line.segments[1].handleIn = _handle.multiply(-1);
+    this.line.segments[1].handleOut = _handle;
+    this.line.segments[2].point = _p1a;
+    this.arrow.rotate(_a - this.arrow_angle);
+    this.arrow.fillColor = _color;
+    this.arrow.position = this.paper_coords.subtract(_u.multiply(4));
+    this.arrow_angle = _a;
+    if (_a > 90) {
+        _a -= 180;
+    }
+    if (_a < -90) {
+        _a += 180;
+    }
+    this.text.rotate(_a - this.text_angle);
+    this.text.content = Rkns.Renderer.Utils.shortenText(this.model.get("title"), Rkns.Renderer._EDGE_MAX_CHAR);
+    this.text.position = this.paper_coords;
+    this.text_angle = _a;
+}
+
+Rkns.Renderer.Edge.prototype.openTooltip = function() {
+    this.renderer.removeRepresentationsOfType("tooltip");
+    var _tooltip = this.renderer.addRepresentation("EdgeTooltip",null);
+    _tooltip.edge_representation = this;
+    _tooltip.draw();
+}
+
+Rkns.Renderer.Edge.prototype.select = function() {
+    this.line.strokeWidth = 3;
+    this.openTooltip();
+    paper.view.draw();
+}
+
+Rkns.Renderer.Edge.prototype.unselect = function() {
+    this.line.strokeWidth = 1;
+    paper.view.draw();
+}
+
+Rkns.Renderer.Edge.prototype.mouseup = function(_event) {
+}
+
+Rkns.Renderer.Edge.prototype.paperShift = function(_delta) {
+    this.from_representation.paperShift(_delta);
+    this.to_representation.paperShift(_delta);
+    this.renderer.redraw();
+}
+
+Rkns.Renderer.Edge.prototype.destroy = function() {
+    this.super("destroy");
+    this.line.remove();
+    this.arrow.remove();
+    this.text.remove();
+    var _this = this;
+    this.bundle.edges = Rkns._(this.bundle.edges).reject(function(_edge) {
+        return _edge === _this;
+    });
+}
+
+/* */
+
+Rkns.Renderer.NodeTooltip = Rkns.Utils.inherit(Rkns.Renderer._BaseRepresentation);
+
+Rkns.Renderer.NodeTooltip.prototype._init = function() {
+    this.renderer.overlay_layer.activate();
+    this.type = "tooltip";
+    this.tooltip_block = new paper.Path();
+    var _pts = Rkns._(Rkns._.range(8)).map(function() {return [0,0]});
+    this.tooltip_block.add.apply(this.tooltip_block, _pts);
+    this.tooltip_block.strokeWidth = 2;
+    this.tooltip_block.strokeColor = "#999999";
+    this.tooltip_block.fillColor = "#e0e0e0";
+    this.tooltip_block.opacity = .8;
+    this.tooltip_$ = Rkns.$('<div>')
+        .appendTo(this.renderer.tooltip_$)
+        .css({
+            position: "absolute",
+            opacity: .8
+        })
+        .hide();
+}
+
+Rkns.Renderer.NodeTooltip.prototype.template = Rkns._.template(
+    '<h2><span class="Rk-CloseX">&times;</span><%=a%></h2>'
+    + '<p><%-description%></p>'
+    + '<ul class="Rk-Related-List"></ul>'
+);
+
+Rkns.Renderer.NodeTooltip.prototype.draw = function() {
+    var _model = this.node_representation.model,
+        _title = _model.get("title"),
+        _uri = _model.get("uri");
+    this.tooltip_$
+        .html(this.template({
+            a: (_uri ? '<a href="' + _uri + '" target="_blank">' : '' ) + _title + (_uri ? '</a>' : '' ),
+            description: _model.get("description").replace(/(\n|\r|\r\n)/mg,' ').substr(0,180).replace(/(^.{150,179})[\s].+$/m,'$1&hellip;')
+        }))
+    var _this = this,
+        _renkan = this.renderer.renkan,
+        _uris = _renkan.uris[_uri];
+    Rkns._(_uris).each(function(_othernode) {
+        if (_othernode !== _model && _othernode.get("project") !== _this.project) {
+            var _otherproj = _othernode.get("project"),
+                _nodetitle = _othernode.get("title") || "Untitled node"
+                _projtitle = _otherproj.get("title") || "Untitled node",
+                _html = _renkan.l10n.see_in_project.replace('{node}',Rkns._.escape(_nodetitle)).replace('{project}',Rkns._.escape(_projtitle)),
+                _li = Rkns.$("<li>").addClass("Rk-Related").html(_html);
+            _li.click(function() {
+                _renkan.renderProject(_otherproj);
+                Rkns._.defer(function() {
+                    _othernode.trigger("select");
+                });
+            });
+            _this.tooltip_$.append(_li);
+        }
+    });
+    this.tooltip_$.find(".Rk-CloseX").click(function() {
+        _this.renderer.removeRepresentation(_this);
+        paper.view.draw();
+    });
+    this.redraw();
+}
+
+Rkns.Renderer.NodeTooltip.prototype.redraw = function() {
+    var _coords = this.node_representation.paper_coords;
+    Rkns.Renderer.Utils.drawTooltip(_coords, this.tooltip_block, 250, 15, this.tooltip_$);
+    this.tooltip_$.show();
+}
+
+Rkns.Renderer.NodeTooltip.prototype.destroy = function() {
+    this.tooltip_block.remove();
+    this.tooltip_$.detach();
+}
+
+/* */
+
+Rkns.Renderer.EdgeTooltip = Rkns.Utils.inherit(Rkns.Renderer._BaseRepresentation);
+
+Rkns.Renderer.EdgeTooltip.prototype._init = function() {
+    this.renderer.overlay_layer.activate();
+    this.type = "tooltip";
+    this.tooltip_block = new paper.Path();
+    var _pts = Rkns._(Rkns._.range(8)).map(function() {return [0,0]});
+    this.tooltip_block.add.apply(this.tooltip_block, _pts);
+    this.tooltip_block.strokeWidth = 2;
+    this.tooltip_block.strokeColor = "#999999";
+    this.tooltip_block.fillColor = "#e0e0e0";
+    this.tooltip_block.opacity = .8;
+    this.tooltip_$ = Rkns.$('<div>')
+        .appendTo(this.renderer.tooltip_$)
+        .css({
+            position: "absolute",
+            opacity: .8
+        })
+        .hide();
+}
+
+Rkns.Renderer.EdgeTooltip.prototype.template = Rkns._.template(
+    '<h2><span class="Rk-CloseX">&times;</span><%=a%></h2>'
+    + '<p><%-description%></p>'
+);
+
+Rkns.Renderer.EdgeTooltip.prototype.draw = function() {
+    var _model = this.edge_representation.model,
+        _title = _model.get("title"),
+        _uri = _model.get("uri");
+    this.tooltip_$
+        .html(this.template({
+            a: (_uri ? '<a href="' + _uri + '" target="_blank">' : '' ) + _title + (_uri ? '</a>' : '' ),
+            description: _model.get("description").replace(/(\n|\r|\r\n)/mg,' ').substr(0,180).replace(/(^.{150,179})[\s].+$/m,'$1&hellip;')
+        }));
+    this.redraw();
+    var _this = this;
+    this.tooltip_$.find(".Rk-CloseX").click(function() {
+        _this.renderer.removeRepresentation(_this);
+        paper.view.draw();
+    });
+}
+
+Rkns.Renderer.EdgeTooltip.prototype.redraw = function() {
+    var _coords = this.edge_representation.paper_coords;
+    Rkns.Renderer.Utils.drawTooltip(_coords, this.tooltip_block, 250, 5, this.tooltip_$);
+    this.tooltip_$.show();
+    paper.view.draw();
+}
+
+Rkns.Renderer.EdgeTooltip.prototype.destroy = function() {
+    this.tooltip_block.remove();
+    this.tooltip_$.detach();
+}
+
+/* */
+
+Rkns.Renderer.Scene = function(_renkan, _project) {
+    this.renkan = _renkan;
+    this.project = _project;
+    this.$ = Rkns.$(".Rk-Render");
+    this.representations = [];
+    this.$.html(this.template({
+        width: this.$.width(),
+        height: this.$.height(),
+        l10n: _renkan.l10n
+    }))
+    this.canvas_$ = this.$.find(".Rk-Canvas");
+    this.tooltip_$ = this.$.find(".Rk-Editor");
+    paper.setup(this.canvas_$[0]);
+    this.scale = 1;
+    this.offset = paper.view.center;
+    this.totalScroll = 0;
+    this.click_target = null;
+    this.selected_target = null;
+    this.edge_layer = new paper.Layer();
+    this.node_layer = new paper.Layer();
+    this.overlay_layer = new paper.Layer();
+    this.bundles = [];
+    var _tool = new paper.Tool(),
+        _this = this;
+    _tool.minDistance = Rkns.Renderer._MIN_DRAG_DISTANCE;
+    _tool.onMouseMove = function(_event) {
+        _this.onMouseMove(_event);
+    }
+    _tool.onMouseDown = function(_event) {
+        _this.onMouseDown(_event);
+    }
+    _tool.onMouseDrag = function(_event) {
+        _this.onMouseDrag(_event);
+    }
+    this.canvas_$.mouseup(function(_event) {
+        _this.onMouseUp(_event);
+    });
+    this.canvas_$.mousewheel(function(_event, _delta) {
+        _this.onScroll(_event, _delta);
+    });
+    this.tooltip_$.find(".Rk-ZoomOut").click(function() {
+        _this.offset = new paper.Point([
+            _this.canvas_$.width(),
+            _this.canvas_$.height()
+        ]).multiply( .5 * ( 1 - Math.SQRT1_2 ) ).add(_this.offset.multiply( Math.SQRT1_2 ));
+        _this.scale *= Math.SQRT1_2;
+        _this.redraw();
+    });
+    this.tooltip_$.find(".Rk-ZoomIn").click(function() {
+        _this.offset = new paper.Point([
+            _this.canvas_$.width(),
+            _this.canvas_$.height()
+        ]).multiply( .5 * ( 1 - Math.SQRT2 ) ).add(_this.offset.multiply( Math.SQRT2 ));
+        _this.scale *= Math.SQRT2;
+        _this.redraw();
+    });
+    paper.view.onResize = function(_event) {
+        _this.offset = _this.offset.add(_event.delta.divide(2));
+        _this.redraw();
+    }
+    
+    var _thRedraw = Rkns._.throttle(function() {
+        _this.redraw();
+    },50);
+    
+    this.addRepresentations("Node", this.project.get("nodes"));
+    this.addRepresentations("Edge", this.project.get("edges"));
+    
+    this._addNodesBinding = function(_node) {
+        _this.addRepresentation("Node", _node);
+        _thRedraw();
+    }
+    this._addEdgesBinding = function(_edge) {
+        _this.addRepresentation("Edge", _edge);
+        _thRedraw();
+    }
+    
+    this.project.on("add:nodes", this._addNodesBinding );
+    this.project.on("add:edges", this._addEdgesBinding );
+    this.redraw();
+}
+
+Rkns.Renderer.Scene.prototype.template = Rkns._.template(
+    '<canvas class="Rk-Canvas" width="<%-width%>" height="<%-height%>"></canvas><div class="Rk-Editor">'
+    + '<div class="Rk-ZoomButtons"><div class="Rk-ZoomIn" title="<%-l10n.zoom_in%>"></div><div class="Rk-ZoomOut" title="<%-l10n.zoom_out%>"></div></div>'
+    + '</div>'
+);
+
+Rkns.Renderer.Scene.prototype.addToBundles = function(_edgeRepr) {
+    var _bundle = Rkns._(this.bundles).find(function(_bundle) {
+        return ( 
+            ( _bundle.from === _edgeRepr.from_representation && _bundle.to === _edgeRepr.to_representation )
+            || ( _bundle.from === _edgeRepr.to_representation && _bundle.to === _edgeRepr.from_representation )
+        );
+    });
+    if (typeof _bundle !== "undefined") {
+        _bundle.edges.push(_edgeRepr)
+    } else {
+        _bundle = {
+            from: _edgeRepr.from_representation,
+            to: _edgeRepr.to_representation,
+            edges: [ _edgeRepr ],
+            getPosition: function(_er) {
+                var _dir = (_er.from_representation === this.from) ? 1 : -1;
+                return _dir * ( Rkns._(this.edges).indexOf(_er) - (this.edges.length - 1) / 2 );
+            }
+        }
+        this.bundles.push(_bundle);
+    }
+    return _bundle;
+}
+
+Rkns.Renderer.Scene.prototype.autoScale = function() {
+    if (this.project.get("nodes").length) {
+        var _xx = this.project.get("nodes").map(function(_node) { return _node.get("position").x }),
+            _yy = this.project.get("nodes").map(function(_node) { return _node.get("position").y }),
+            _minx = Math.min.apply(Math, _xx),
+            _miny = Math.min.apply(Math, _yy),
+            _maxx = Math.max.apply(Math, _xx),
+            _maxy = Math.max.apply(Math, _yy);
+        this.scale = Math.min((paper.view.size.width - 2 * Rkns.Renderer._MARGIN_X) / (_maxx - _minx), (paper.view.size.height - 2 * Rkns.Renderer._MARGIN_Y) / (_maxy - _miny));
+        this.offset = paper.view.center.subtract(new paper.Point([(_maxx + _minx) / 2, (_maxy + _miny) / 2]).multiply(this.scale));
+        this.redraw();
+    }
+}
+
+Rkns.Renderer.Scene.prototype.toPaperCoords = function(_point) {
+    return _point.multiply(this.scale).add(this.offset);
+}
+
+
+Rkns.Renderer.Scene.prototype.toModelCoords = function(_point) {
+    return _point.subtract(this.offset).divide(this.scale);
+}
+
+Rkns.Renderer.Scene.prototype.addRepresentation = function(_type, _model) {
+    var _repr = new Rkns.Renderer[_type](this, _model);
+    this.representations.push(_repr);
+    return _repr;
+}
+
+Rkns.Renderer.Scene.prototype.addRepresentations = function(_type, _collection) {
+    var _this = this;
+    _collection.forEach(function(_model) {
+        _this.addRepresentation(_type, _model);
+    });
+}
+
+Rkns.Renderer.Scene.prototype.removeRepresentation = function(_representation) {
+    _representation.destroy();
+    this.representations = Rkns._(this.representations).reject(
+        function(_repr) {
+            return _repr == _representation
+        }
+    );
+}
+
+Rkns.Renderer.Scene.prototype.getRepresentationByModel = function(_model) {
+    return Rkns._(this.representations).find(function(_repr) {
+        return _repr.model === _model;
+    });
+}
+
+Rkns.Renderer.Scene.prototype.removeRepresentationsOfType = function(_type) {
+    var _representations = Rkns._(this.representations).filter(function(_repr) {
+            return _repr.type == _type;
+        }),
+        _this = this;
+    Rkns._(_representations).each(function(_repr) {
+        _this.removeRepresentation(_repr);
+    });
+}
+
+Rkns.Renderer.Scene.prototype.unselectAll = function() {
+    Rkns._(this.representations).each(function(_repr) {
+        _repr.model.trigger("unselect");
+    });
+}
+
+Rkns.Renderer.Scene.prototype.redraw = function() {
+    Rkns._(this.representations).each(function(_representation) {
+        _representation.redraw();
+    });
+    paper.view.draw();
+}
+
+Rkns.Renderer.Scene.prototype.addTempEdge = function(_from, _point) {
+    var _tmpEdge = this.addRepresentation("TempEdge",null);
+    _tmpEdge.end_pos = _point;
+    _tmpEdge.from_representation = _from;
+    _tmpEdge.redraw();
+    this.click_target = _tmpEdge;
+}
+
+Rkns.Renderer.Scene.prototype.findTarget = function(_hitResult) {
+    if (_hitResult && typeof _hitResult.item.__representation !== "undefined") {
+        var _newTarget = _hitResult.item.__representation;
+        if (this.selected_target !== _newTarget && _newTarget.model) {
+            if (this.selected_target) {
+                this.selected_target.model.trigger("unselect");
+            }
+            _newTarget.model.trigger("select");
+            this.selected_target = _newTarget;
+        }
+    } else {
+        if (!_hitResult) {
+            this.removeRepresentationsOfType("tooltip");
+        }
+        if (this.selected_target) {
+            this.selected_target.model.trigger("unselect");
+        }
+        this.selected_target = null;
+    }
+}
+
+Rkns.Renderer.Scene.prototype.onMouseMove = function(_event) {
+    var _hitResult = paper.project.hitTest(_event.point);
+    if (this.is_dragging) {
+        if (this.click_target && typeof this.click_target.paperShift === "function") {
+            this.click_target.paperShift(_event.delta);
+        } else {
+            this.offset = this.offset.add(_event.delta);
+            this.redraw();
+        }
+    } else {
+        this.findTarget(_hitResult);
+    }
+}
+
+Rkns.Renderer.Scene.prototype.onMouseDown = function(_event) {
+    this.is_dragging = false;
+    var _hitResult = paper.project.hitTest(_event.point);
+    if (_hitResult && typeof _hitResult.item.__representation !== "undefined") {
+        this.click_target = _hitResult.item.__representation;
+    } else {
+        this.click_target = null;
+    }
+}
+
+Rkns.Renderer.Scene.prototype.onMouseDrag = function(_event) {
+    this.is_dragging = true;
+    this.onMouseMove(_event);
+}
+
+Rkns.Renderer.Scene.prototype.onMouseUp = function(_event) {
+    if (this.click_target) {
+        var _off = this.canvas_$.offset();
+        if (this.click_target.model) {
+            this.click_target.model.trigger("click");
+        }
+    }
+    this.is_dragging = false;
+    this.click_target = null;
+}
+
+Rkns.Renderer.Scene.prototype.onScroll = function(_event, _scrolldelta) {
+    this.totalScroll += _scrolldelta;
+    if (Math.abs(this.totalScroll) >= 1) {
+        var _off = this.canvas_$.offset(),
+            _delta = new paper.Point([
+                _event.pageX - _off.left,
+                _event.pageY - _off.top
+            ]).subtract(this.offset).multiply( Math.SQRT2 - 1 );
+        if (this.totalScroll > 0) {
+            this.offset = this.offset.subtract(_delta);
+            this.scale *= Math.SQRT2;
+        } else {
+            this.offset = this.offset.add(_delta.divide( Math.SQRT2 ));
+            this.scale *= Math.SQRT1_2;
+        }
+        this.totalScroll = 0;
+        this.redraw();
+    }
+}
+
+Rkns.Renderer.Scene.prototype.destroy = function() {
+    this.project.off("add:nodes", this._addNodesBinding );
+    this.project.off("add:edges", this._addEdgesBinding );
+    Rkns._(this.representations).each(function(_repr) {
+        _repr.destroy();
+    });
+    this.$.html("");
+    paper.remove();
+}
+
--- a/metadataplayer/libs/underscore-min.js	Fri Aug 31 17:43:31 2012 +0200
+++ b/metadataplayer/libs/underscore-min.js	Thu Sep 27 12:23:41 2012 +0200
@@ -1,30 +1,32 @@
-// Underscore.js 1.2.3
-// (c) 2009-2011 Jeremy Ashkenas, DocumentCloud Inc.
+// Underscore.js 1.3.3
+// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
 // Underscore is freely distributable under the MIT license.
 // Portions of Underscore are inspired or borrowed from Prototype,
 // Oliver Steele's Functional, and John Resig's Micro-Templating.
 // For all details and documentation:
 // http://documentcloud.github.com/underscore
-(function(){function r(a,c,d){if(a===c)return a!==0||1/a==1/c;if(a==null||c==null)return a===c;if(a._chain)a=a._wrapped;if(c._chain)c=c._wrapped;if(a.isEqual&&b.isFunction(a.isEqual))return a.isEqual(c);if(c.isEqual&&b.isFunction(c.isEqual))return c.isEqual(a);var e=l.call(a);if(e!=l.call(c))return false;switch(e){case "[object String]":return a==String(c);case "[object Number]":return a!=+a?c!=+c:a==0?1/a==1/c:a==+c;case "[object Date]":case "[object Boolean]":return+a==+c;case "[object RegExp]":return a.source==
-c.source&&a.global==c.global&&a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase}if(typeof a!="object"||typeof c!="object")return false;for(var f=d.length;f--;)if(d[f]==a)return true;d.push(a);var f=0,g=true;if(e=="[object Array]"){if(f=a.length,g=f==c.length)for(;f--;)if(!(g=f in a==f in c&&r(a[f],c[f],d)))break}else{if("constructor"in a!="constructor"in c||a.constructor!=c.constructor)return false;for(var h in a)if(m.call(a,h)&&(f++,!(g=m.call(c,h)&&r(a[h],c[h],d))))break;if(g){for(h in c)if(m.call(c,
-h)&&!f--)break;g=!f}}d.pop();return g}var s=this,F=s._,o={},k=Array.prototype,p=Object.prototype,i=k.slice,G=k.concat,H=k.unshift,l=p.toString,m=p.hasOwnProperty,v=k.forEach,w=k.map,x=k.reduce,y=k.reduceRight,z=k.filter,A=k.every,B=k.some,q=k.indexOf,C=k.lastIndexOf,p=Array.isArray,I=Object.keys,t=Function.prototype.bind,b=function(a){return new n(a)};if(typeof exports!=="undefined"){if(typeof module!=="undefined"&&module.exports)exports=module.exports=b;exports._=b}else typeof define==="function"&&
-define.amd?define("underscore",function(){return b}):s._=b;b.VERSION="1.2.3";var j=b.each=b.forEach=function(a,c,b){if(a!=null)if(v&&a.forEach===v)a.forEach(c,b);else if(a.length===+a.length)for(var e=0,f=a.length;e<f;e++){if(e in a&&c.call(b,a[e],e,a)===o)break}else for(e in a)if(m.call(a,e)&&c.call(b,a[e],e,a)===o)break};b.map=function(a,c,b){var e=[];if(a==null)return e;if(w&&a.map===w)return a.map(c,b);j(a,function(a,g,h){e[e.length]=c.call(b,a,g,h)});return e};b.reduce=b.foldl=b.inject=function(a,
-c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(x&&a.reduce===x)return e&&(c=b.bind(c,e)),f?a.reduce(c,d):a.reduce(c);j(a,function(a,b,i){f?d=c.call(e,d,a,b,i):(d=a,f=true)});if(!f)throw new TypeError("Reduce of empty array with no initial value");return d};b.reduceRight=b.foldr=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(y&&a.reduceRight===y)return e&&(c=b.bind(c,e)),f?a.reduceRight(c,d):a.reduceRight(c);var g=b.toArray(a).reverse();e&&!f&&(c=b.bind(c,e));return f?b.reduce(g,
-c,d,e):b.reduce(g,c)};b.find=b.detect=function(a,c,b){var e;D(a,function(a,g,h){if(c.call(b,a,g,h))return e=a,true});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(z&&a.filter===z)return a.filter(c,b);j(a,function(a,g,h){c.call(b,a,g,h)&&(e[e.length]=a)});return e};b.reject=function(a,c,b){var e=[];if(a==null)return e;j(a,function(a,g,h){c.call(b,a,g,h)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=true;if(a==null)return e;if(A&&a.every===A)return a.every(c,
-b);j(a,function(a,g,h){if(!(e=e&&c.call(b,a,g,h)))return o});return e};var D=b.some=b.any=function(a,c,d){c||(c=b.identity);var e=false;if(a==null)return e;if(B&&a.some===B)return a.some(c,d);j(a,function(a,b,h){if(e||(e=c.call(d,a,b,h)))return o});return!!e};b.include=b.contains=function(a,c){var b=false;if(a==null)return b;return q&&a.indexOf===q?a.indexOf(c)!=-1:b=D(a,function(a){return a===c})};b.invoke=function(a,c){var d=i.call(arguments,2);return b.map(a,function(a){return(c.call?c||a:a[c]).apply(a,
-d)})};b.pluck=function(a,c){return b.map(a,function(a){return a[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a))return Math.max.apply(Math,a);if(!c&&b.isEmpty(a))return-Infinity;var e={computed:-Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b<e.computed&&(e={value:a,
-computed:b})});return e.value};b.shuffle=function(a){var c=[],b;j(a,function(a,f){f==0?c[0]=a:(b=Math.floor(Math.random()*(f+1)),c[f]=c[b],c[b]=a)});return c};b.sortBy=function(a,c,d){return b.pluck(b.map(a,function(a,b,g){return{value:a,criteria:c.call(d,a,b,g)}}).sort(function(a,c){var b=a.criteria,d=c.criteria;return b<d?-1:b>d?1:0}),"value")};b.groupBy=function(a,c){var d={},e=b.isFunction(c)?c:function(a){return a[c]};j(a,function(a,b){var c=e(a,b);(d[c]||(d[c]=[])).push(a)});return d};b.sortedIndex=
-function(a,c,d){d||(d=b.identity);for(var e=0,f=a.length;e<f;){var g=e+f>>1;d(a[g])<d(c)?e=g+1:f=g}return e};b.toArray=function(a){return!a?[]:a.toArray?a.toArray():b.isArray(a)?i.call(a):b.isArguments(a)?i.call(a):b.values(a)};b.size=function(a){return b.toArray(a).length};b.first=b.head=function(a,b,d){return b!=null&&!d?i.call(a,0,b):a[0]};b.initial=function(a,b,d){return i.call(a,0,a.length-(b==null||d?1:b))};b.last=function(a,b,d){return b!=null&&!d?i.call(a,Math.max(a.length-b,0)):a[a.length-
-1]};b.rest=b.tail=function(a,b,d){return i.call(a,b==null||d?1:b)};b.compact=function(a){return b.filter(a,function(a){return!!a})};b.flatten=function(a,c){return b.reduce(a,function(a,e){if(b.isArray(e))return a.concat(c?e:b.flatten(e));a[a.length]=e;return a},[])};b.without=function(a){return b.difference(a,i.call(arguments,1))};b.uniq=b.unique=function(a,c,d){var d=d?b.map(a,d):a,e=[];b.reduce(d,function(d,g,h){if(0==h||(c===true?b.last(d)!=g:!b.include(d,g)))d[d.length]=g,e[e.length]=a[h];return d},
-[]);return e};b.union=function(){return b.uniq(b.flatten(arguments,true))};b.intersection=b.intersect=function(a){var c=i.call(arguments,1);return b.filter(b.uniq(a),function(a){return b.every(c,function(c){return b.indexOf(c,a)>=0})})};b.difference=function(a){var c=b.flatten(i.call(arguments,1));return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=i.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e<c;e++)d[e]=b.pluck(a,""+e);return d};b.indexOf=function(a,
-c,d){if(a==null)return-1;var e;if(d)return d=b.sortedIndex(a,c),a[d]===c?d:-1;if(q&&a.indexOf===q)return a.indexOf(c);for(d=0,e=a.length;d<e;d++)if(d in a&&a[d]===c)return d;return-1};b.lastIndexOf=function(a,b){if(a==null)return-1;if(C&&a.lastIndexOf===C)return a.lastIndexOf(b);for(var d=a.length;d--;)if(d in a&&a[d]===b)return d;return-1};b.range=function(a,b,d){arguments.length<=1&&(b=a||0,a=0);for(var d=arguments[2]||1,e=Math.max(Math.ceil((b-a)/d),0),f=0,g=Array(e);f<e;)g[f++]=a,a+=d;return g};
-var E=function(){};b.bind=function(a,c){var d,e;if(a.bind===t&&t)return t.apply(a,i.call(arguments,1));if(!b.isFunction(a))throw new TypeError;e=i.call(arguments,2);return d=function(){if(!(this instanceof d))return a.apply(c,e.concat(i.call(arguments)));E.prototype=a.prototype;var b=new E,g=a.apply(b,e.concat(i.call(arguments)));return Object(g)===g?g:b}};b.bindAll=function(a){var c=i.call(arguments,1);c.length==0&&(c=b.functions(a));j(c,function(c){a[c]=b.bind(a[c],a)});return a};b.memoize=function(a,
-c){var d={};c||(c=b.identity);return function(){var b=c.apply(this,arguments);return m.call(d,b)?d[b]:d[b]=a.apply(this,arguments)}};b.delay=function(a,b){var d=i.call(arguments,2);return setTimeout(function(){return a.apply(a,d)},b)};b.defer=function(a){return b.delay.apply(b,[a,1].concat(i.call(arguments,1)))};b.throttle=function(a,c){var d,e,f,g,h,i=b.debounce(function(){h=g=false},c);return function(){d=this;e=arguments;var b;f||(f=setTimeout(function(){f=null;h&&a.apply(d,e);i()},c));g?h=true:
-a.apply(d,e);i();g=true}};b.debounce=function(a,b){var d;return function(){var e=this,f=arguments;clearTimeout(d);d=setTimeout(function(){d=null;a.apply(e,f)},b)}};b.once=function(a){var b=false,d;return function(){if(b)return d;b=true;return d=a.apply(this,arguments)}};b.wrap=function(a,b){return function(){var d=G.apply([a],arguments);return b.apply(this,d)}};b.compose=function(){var a=arguments;return function(){for(var b=arguments,d=a.length-1;d>=0;d--)b=[a[d].apply(this,b)];return b[0]}};b.after=
-function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=I||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var b=[],d;for(d in a)m.call(a,d)&&(b[b.length]=d);return b};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&c.push(d);return c.sort()};b.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)b[d]!==void 0&&(a[d]=b[d])});return a};b.defaults=function(a){j(i.call(arguments,
-1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)?a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return r(a,b,[])};b.isEmpty=function(a){if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(m.call(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=p||function(a){return l.call(a)=="[object Array]"};b.isObject=function(a){return a===
-Object(a)};b.isArguments=function(a){return l.call(a)=="[object Arguments]"};if(!b.isArguments(arguments))b.isArguments=function(a){return!(!a||!m.call(a,"callee"))};b.isFunction=function(a){return l.call(a)=="[object Function]"};b.isString=function(a){return l.call(a)=="[object String]"};b.isNumber=function(a){return l.call(a)=="[object Number]"};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||l.call(a)=="[object Boolean]"};b.isDate=function(a){return l.call(a)==
-"[object Date]"};b.isRegExp=function(a){return l.call(a)=="[object RegExp]"};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.noConflict=function(){s._=F;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e<a;e++)b.call(d,e)};b.escape=function(a){return(""+a).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#x27;").replace(/\//g,"&#x2F;")};b.mixin=function(a){j(b.functions(a),function(c){J(c,
-b[c]=a[c])})};var K=0;b.uniqueId=function(a){var b=K++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};b.template=function(a,c){var d=b.templateSettings,d="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(d.escape,function(a,b){return"',_.escape("+b.replace(/\\'/g,"'")+"),'"}).replace(d.interpolate,function(a,b){return"',"+b.replace(/\\'/g,
-"'")+",'"}).replace(d.evaluate||null,function(a,b){return"');"+b.replace(/\\'/g,"'").replace(/[\r\n\t]/g," ")+";__p.push('"}).replace(/\r/g,"\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');",e=new Function("obj","_",d);return c?e(c,b):function(a){return e.call(this,a,b)}};var n=function(a){this._wrapped=a};b.prototype=n.prototype;var u=function(a,c){return c?b(a).chain():a},J=function(a,c){n.prototype[a]=function(){var a=i.call(arguments);H.call(a,this._wrapped);return u(c.apply(b,
-a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];n.prototype[a]=function(){b.apply(this._wrapped,arguments);return u(this._wrapped,this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];n.prototype[a]=function(){return u(b.apply(this._wrapped,arguments),this._chain)}});n.prototype.chain=function(){this._chain=true;return this};n.prototype.value=function(){return this._wrapped}}).call(this);
+(function(){function r(a,c,d){if(a===c)return 0!==a||1/a==1/c;if(null==a||null==c)return a===c;a._chain&&(a=a._wrapped);c._chain&&(c=c._wrapped);if(a.isEqual&&b.isFunction(a.isEqual))return a.isEqual(c);if(c.isEqual&&b.isFunction(c.isEqual))return c.isEqual(a);var e=l.call(a);if(e!=l.call(c))return!1;switch(e){case "[object String]":return a==""+c;case "[object Number]":return a!=+a?c!=+c:0==a?1/a==1/c:a==+c;case "[object Date]":case "[object Boolean]":return+a==+c;case "[object RegExp]":return a.source==
+c.source&&a.global==c.global&&a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase}if("object"!=typeof a||"object"!=typeof c)return!1;for(var f=d.length;f--;)if(d[f]==a)return!0;d.push(a);var f=0,g=!0;if("[object Array]"==e){if(f=a.length,g=f==c.length)for(;f--&&(g=f in a==f in c&&r(a[f],c[f],d)););}else{if("constructor"in a!="constructor"in c||a.constructor!=c.constructor)return!1;for(var h in a)if(b.has(a,h)&&(f++,!(g=b.has(c,h)&&r(a[h],c[h],d))))break;if(g){for(h in c)if(b.has(c,h)&&!f--)break;
+g=!f}}d.pop();return g}var s=this,I=s._,o={},k=Array.prototype,p=Object.prototype,i=k.slice,J=k.unshift,l=p.toString,K=p.hasOwnProperty,y=k.forEach,z=k.map,A=k.reduce,B=k.reduceRight,C=k.filter,D=k.every,E=k.some,q=k.indexOf,F=k.lastIndexOf,p=Array.isArray,L=Object.keys,t=Function.prototype.bind,b=function(a){return new m(a)};"undefined"!==typeof exports?("undefined"!==typeof module&&module.exports&&(exports=module.exports=b),exports._=b):s._=b;b.VERSION="1.3.3";var j=b.each=b.forEach=function(a,
+c,d){if(a!=null)if(y&&a.forEach===y)a.forEach(c,d);else if(a.length===+a.length)for(var e=0,f=a.length;e<f;e++){if(e in a&&c.call(d,a[e],e,a)===o)break}else for(e in a)if(b.has(a,e)&&c.call(d,a[e],e,a)===o)break};b.map=b.collect=function(a,c,b){var e=[];if(a==null)return e;if(z&&a.map===z)return a.map(c,b);j(a,function(a,g,h){e[e.length]=c.call(b,a,g,h)});if(a.length===+a.length)e.length=a.length;return e};b.reduce=b.foldl=b.inject=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(A&&
+a.reduce===A){e&&(c=b.bind(c,e));return f?a.reduce(c,d):a.reduce(c)}j(a,function(a,b,i){if(f)d=c.call(e,d,a,b,i);else{d=a;f=true}});if(!f)throw new TypeError("Reduce of empty array with no initial value");return d};b.reduceRight=b.foldr=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(B&&a.reduceRight===B){e&&(c=b.bind(c,e));return f?a.reduceRight(c,d):a.reduceRight(c)}var g=b.toArray(a).reverse();e&&!f&&(c=b.bind(c,e));return f?b.reduce(g,c,d,e):b.reduce(g,c)};b.find=b.detect=function(a,
+c,b){var e;G(a,function(a,g,h){if(c.call(b,a,g,h)){e=a;return true}});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(C&&a.filter===C)return a.filter(c,b);j(a,function(a,g,h){c.call(b,a,g,h)&&(e[e.length]=a)});return e};b.reject=function(a,c,b){var e=[];if(a==null)return e;j(a,function(a,g,h){c.call(b,a,g,h)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=true;if(a==null)return e;if(D&&a.every===D)return a.every(c,b);j(a,function(a,g,h){if(!(e=e&&c.call(b,
+a,g,h)))return o});return!!e};var G=b.some=b.any=function(a,c,d){c||(c=b.identity);var e=false;if(a==null)return e;if(E&&a.some===E)return a.some(c,d);j(a,function(a,b,h){if(e||(e=c.call(d,a,b,h)))return o});return!!e};b.include=b.contains=function(a,c){var b=false;if(a==null)return b;if(q&&a.indexOf===q)return a.indexOf(c)!=-1;return b=G(a,function(a){return a===c})};b.invoke=function(a,c){var d=i.call(arguments,2);return b.map(a,function(a){return(b.isFunction(c)?c||a:a[c]).apply(a,d)})};b.pluck=
+function(a,c){return b.map(a,function(a){return a[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a)&&a[0]===+a[0])return Math.max.apply(Math,a);if(!c&&b.isEmpty(a))return-Infinity;var e={computed:-Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a)&&a[0]===+a[0])return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b<e.computed&&
+(e={value:a,computed:b})});return e.value};b.shuffle=function(a){var b=[],d;j(a,function(a,f){d=Math.floor(Math.random()*(f+1));b[f]=b[d];b[d]=a});return b};b.sortBy=function(a,c,d){var e=b.isFunction(c)?c:function(a){return a[c]};return b.pluck(b.map(a,function(a,b,c){return{value:a,criteria:e.call(d,a,b,c)}}).sort(function(a,b){var c=a.criteria,d=b.criteria;return c===void 0?1:d===void 0?-1:c<d?-1:c>d?1:0}),"value")};b.groupBy=function(a,c){var d={},e=b.isFunction(c)?c:function(a){return a[c]};
+j(a,function(a,b){var c=e(a,b);(d[c]||(d[c]=[])).push(a)});return d};b.sortedIndex=function(a,c,d){d||(d=b.identity);for(var e=0,f=a.length;e<f;){var g=e+f>>1;d(a[g])<d(c)?e=g+1:f=g}return e};b.toArray=function(a){return!a?[]:b.isArray(a)||b.isArguments(a)?i.call(a):a.toArray&&b.isFunction(a.toArray)?a.toArray():b.values(a)};b.size=function(a){return b.isArray(a)?a.length:b.keys(a).length};b.first=b.head=b.take=function(a,b,d){return b!=null&&!d?i.call(a,0,b):a[0]};b.initial=function(a,b,d){return i.call(a,
+0,a.length-(b==null||d?1:b))};b.last=function(a,b,d){return b!=null&&!d?i.call(a,Math.max(a.length-b,0)):a[a.length-1]};b.rest=b.tail=function(a,b,d){return i.call(a,b==null||d?1:b)};b.compact=function(a){return b.filter(a,function(a){return!!a})};b.flatten=function(a,c){return b.reduce(a,function(a,e){if(b.isArray(e))return a.concat(c?e:b.flatten(e));a[a.length]=e;return a},[])};b.without=function(a){return b.difference(a,i.call(arguments,1))};b.uniq=b.unique=function(a,c,d){var d=d?b.map(a,d):a,
+e=[];a.length<3&&(c=true);b.reduce(d,function(d,g,h){if(c?b.last(d)!==g||!d.length:!b.include(d,g)){d.push(g);e.push(a[h])}return d},[]);return e};b.union=function(){return b.uniq(b.flatten(arguments,true))};b.intersection=b.intersect=function(a){var c=i.call(arguments,1);return b.filter(b.uniq(a),function(a){return b.every(c,function(c){return b.indexOf(c,a)>=0})})};b.difference=function(a){var c=b.flatten(i.call(arguments,1),true);return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=
+i.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e<c;e++)d[e]=b.pluck(a,""+e);return d};b.indexOf=function(a,c,d){if(a==null)return-1;var e;if(d){d=b.sortedIndex(a,c);return a[d]===c?d:-1}if(q&&a.indexOf===q)return a.indexOf(c);d=0;for(e=a.length;d<e;d++)if(d in a&&a[d]===c)return d;return-1};b.lastIndexOf=function(a,b){if(a==null)return-1;if(F&&a.lastIndexOf===F)return a.lastIndexOf(b);for(var d=a.length;d--;)if(d in a&&a[d]===b)return d;return-1};b.range=function(a,b,d){if(arguments.length<=
+1){b=a||0;a=0}for(var d=arguments[2]||1,e=Math.max(Math.ceil((b-a)/d),0),f=0,g=Array(e);f<e;){g[f++]=a;a=a+d}return g};var H=function(){};b.bind=function(a,c){var d,e;if(a.bind===t&&t)return t.apply(a,i.call(arguments,1));if(!b.isFunction(a))throw new TypeError;e=i.call(arguments,2);return d=function(){if(!(this instanceof d))return a.apply(c,e.concat(i.call(arguments)));H.prototype=a.prototype;var b=new H,g=a.apply(b,e.concat(i.call(arguments)));return Object(g)===g?g:b}};b.bindAll=function(a){var c=
+i.call(arguments,1);c.length==0&&(c=b.functions(a));j(c,function(c){a[c]=b.bind(a[c],a)});return a};b.memoize=function(a,c){var d={};c||(c=b.identity);return function(){var e=c.apply(this,arguments);return b.has(d,e)?d[e]:d[e]=a.apply(this,arguments)}};b.delay=function(a,b){var d=i.call(arguments,2);return setTimeout(function(){return a.apply(null,d)},b)};b.defer=function(a){return b.delay.apply(b,[a,1].concat(i.call(arguments,1)))};b.throttle=function(a,c){var d,e,f,g,h,i,j=b.debounce(function(){h=
+g=false},c);return function(){d=this;e=arguments;f||(f=setTimeout(function(){f=null;h&&a.apply(d,e);j()},c));g?h=true:i=a.apply(d,e);j();g=true;return i}};b.debounce=function(a,b,d){var e;return function(){var f=this,g=arguments;d&&!e&&a.apply(f,g);clearTimeout(e);e=setTimeout(function(){e=null;d||a.apply(f,g)},b)}};b.once=function(a){var b=false,d;return function(){if(b)return d;b=true;return d=a.apply(this,arguments)}};b.wrap=function(a,b){return function(){var d=[a].concat(i.call(arguments,0));
+return b.apply(this,d)}};b.compose=function(){var a=arguments;return function(){for(var b=arguments,d=a.length-1;d>=0;d--)b=[a[d].apply(this,b)];return b[0]}};b.after=function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=L||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var c=[],d;for(d in a)b.has(a,d)&&(c[c.length]=d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&
+c.push(d);return c.sort()};b.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]=b[d]});return a};b.pick=function(a){var c={};j(b.flatten(i.call(arguments,1)),function(b){b in a&&(c[b]=a[b])});return c};b.defaults=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)?a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return r(a,b,[])};b.isEmpty=
+function(a){if(a==null)return true;if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(b.has(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=p||function(a){return l.call(a)=="[object Array]"};b.isObject=function(a){return a===Object(a)};b.isArguments=function(a){return l.call(a)=="[object Arguments]"};b.isArguments(arguments)||(b.isArguments=function(a){return!(!a||!b.has(a,"callee"))});b.isFunction=function(a){return l.call(a)=="[object Function]"};
+b.isString=function(a){return l.call(a)=="[object String]"};b.isNumber=function(a){return l.call(a)=="[object Number]"};b.isFinite=function(a){return b.isNumber(a)&&isFinite(a)};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||l.call(a)=="[object Boolean]"};b.isDate=function(a){return l.call(a)=="[object Date]"};b.isRegExp=function(a){return l.call(a)=="[object RegExp]"};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.has=function(a,
+b){return K.call(a,b)};b.noConflict=function(){s._=I;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e<a;e++)b.call(d,e)};b.escape=function(a){return(""+a).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#x27;").replace(/\//g,"&#x2F;")};b.result=function(a,c){if(a==null)return null;var d=a[c];return b.isFunction(d)?d.call(a):d};b.mixin=function(a){j(b.functions(a),function(c){M(c,b[c]=a[c])})};var N=0;b.uniqueId=
+function(a){var b=N++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var u=/.^/,n={"\\":"\\","'":"'",r:"\r",n:"\n",t:"\t",u2028:"\u2028",u2029:"\u2029"},v;for(v in n)n[n[v]]=v;var O=/\\|'|\r|\n|\t|\u2028|\u2029/g,P=/\\(\\|'|r|n|t|u2028|u2029)/g,w=function(a){return a.replace(P,function(a,b){return n[b]})};b.template=function(a,c,d){d=b.defaults(d||{},b.templateSettings);a="__p+='"+a.replace(O,function(a){return"\\"+n[a]}).replace(d.escape||
+u,function(a,b){return"'+\n_.escape("+w(b)+")+\n'"}).replace(d.interpolate||u,function(a,b){return"'+\n("+w(b)+")+\n'"}).replace(d.evaluate||u,function(a,b){return"';\n"+w(b)+"\n;__p+='"})+"';\n";d.variable||(a="with(obj||{}){\n"+a+"}\n");var a="var __p='';var print=function(){__p+=Array.prototype.join.call(arguments, '')};\n"+a+"return __p;\n",e=new Function(d.variable||"obj","_",a);if(c)return e(c,b);c=function(a){return e.call(this,a,b)};c.source="function("+(d.variable||"obj")+"){\n"+a+"}";return c};
+b.chain=function(a){return b(a).chain()};var m=function(a){this._wrapped=a};b.prototype=m.prototype;var x=function(a,c){return c?b(a).chain():a},M=function(a,c){m.prototype[a]=function(){var a=i.call(arguments);J.call(a,this._wrapped);return x(c.apply(b,a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];m.prototype[a]=function(){var d=this._wrapped;b.apply(d,arguments);var e=d.length;(a=="shift"||a=="splice")&&e===0&&delete d[0];return x(d,
+this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];m.prototype[a]=function(){return x(b.apply(this._wrapped,arguments),this._chain)}});m.prototype.chain=function(){this._chain=true;return this};m.prototype.value=function(){return this._wrapped}}).call(this);
--- a/metadataplayer/mashup/moon.htm	Fri Aug 31 17:43:31 2012 +0200
+++ b/metadataplayer/mashup/moon.htm	Thu Sep 27 12:23:41 2012 +0200
@@ -37,6 +37,7 @@
             </div>
             <div class="coldroite">
                 <div id="mediaList"></div>
+                <div id="AnnotationsListContainer"></div>
             </div>
             <div class="footer">
                 <hr />
@@ -53,48 +54,47 @@
     format: 'ldt'
 };
 var _canPlayMp4 = document.createElement('video').canPlayType('video/mp4');
-var _config = {            
-    gui: {
-        width : 630,
-        container : 'LdtPlayer',
-        default_options: {
-            metadata: _metadata
+var _config = {
+    width : 630,
+    container : 'LdtPlayer',
+    default_options: {
+        metadata: _metadata
+    },
+    css : '../metadataplayer/LdtPlayer-core.css',
+    widgets: [
+        {
+            type: "MashupPlayer",
+//            split_screen: true,
+            url_transform: function(_url) {
+                if (_canPlayMp4 == "maybe" || _canPlayMp4 == "probably") {
+                    return _url.replace(/\.webm$/i,'.mp4');
+                } else {
+                    return _url.replace(/\.mp4$/i,'.webm');
+                }
+                
+            }
         },
-        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',
-        url_transform: function(_url) {
-            if (_canPlayMp4 == "maybe" || _canPlayMp4 == "probably") {
-                return _url.replace(/\.webm$/i,'.mp4');
-            } else {
-                return _url.replace(/\.mp4$/i,'.webm');
-            }
-            
+        { type: "Slider" },
+        { type: "Controller" },
+        {
+            type: "Segments",
+            annotation_type: false
+        },
+        {
+            type: "Annotation",
+            annotation_type: false
         },
-        height: 350,
-        width: 630,
-        autostart: true,
-        metadata: _metadata
-    }
+        {
+            type: "MediaList",
+            container: "mediaList"
+        },
+        {
+            type: "AnnotationsList",
+            container: "AnnotationsListContainer",
+            default_thumbnail : "http://ldt.iri.centrepompidou.fr/static/site/ldt/css/imgs/video_sequence.png"
+        },
+        { type: "Mediafragment" }
+    ]
 };
 
 _myPlayer = new IriSP.Metadataplayer(_config);
--- a/metadataplayer/mashup/player-html.htm	Fri Aug 31 17:43:31 2012 +0200
+++ b/metadataplayer/mashup/player-html.htm	Thu Sep 27 12:23:41 2012 +0200
@@ -48,71 +48,78 @@
         </div>
     <script type="text/javascript">
 
+function url_transform_func(_url){
+	//_url = "rtmp://media.iri.centrepompidou.fr/ddc_player/mp4:video/ldtplatform/rsln_clay_shirky";
+	// We change the streamer if needed
+    var _transformed = _url.replace(/^rtmp:\/\/media.iri.centrepompidou.fr\/ddc_player\/(mp4:)?video\//i,"http://media.iri.centrepompidou.fr/video/");
+	// Get the file name (= remove extension if necessary)
+	ext = _transformed.substr(_transformed.lastIndexOf('.')+1).toLowerCase();
+	if(ext=="mp4" || ext=="f4v" || ext=="flv" || ext=="mov" || ext=="webm" || ext=="mpg4" || ext=="ogv" || ext=="ogg" || ext=="avi"){
+		_transformed = _transformed.substr(0, _transformed.lastIndexOf('.'));
+	}
+	// now that the file name is clean, we add all the extension and mimetype
+	srcTypeArray = [
+	   {src:_transformed+".mp4", type:"video/mp4"},
+	   {src:_transformed+".ogv", type:"video/ogg"},
+	   {src:_transformed+".webm", type:"video/webm"}
+	];
+    console.log("_transformed = " + srcTypeArray[0]["src"]);
+    return srcTypeArray;
+}
+    
+    
 IriSP.libFiles.defaultDir = "../libs/";
 IriSP.widgetsDir = "../metadataplayer";
 IriSP.language = 'fr';
 var _metadata = {
-//    url: 'bab_files/mashup.json',
+    url: 'bab_files/mashup.json',
 //    url: 'http://ldt.iri.centrepompidou.fr/ldtplatform/ldt/cljson/id/b2754186-a0c9-11e0-b8bd-00145ea49a02?callback=?',
-    url: 'http://ldt.iri.centrepompidou.fr/ldtplatform/ldt/cljson/id/5afd8bbe-9b75-11e1-9e5d-00145ea4a2be?callback=?',
+//    url: 'http://ldt.iri.centrepompidou.fr/ldtplatform/ldt/cljson/id/5afd8bbe-9b75-11e1-9e5d-00145ea4a2be?callback=?',
+//	  url: 'http://localhost/pf/ldtplatform/ldt/mashupbytag/?tag=projection',
+//    url: 'http://ldt.iri.centrepompidou.fr/ldtplatform/ldt/mashupbytag/?tag=mashuptag&callback=?',
+//    url: 'http://localhost/pf/ldtplatform/ldt/mashupbytag/?tag=mashuptag&in=240000&out=1860000',
     format: 'ldt'
 };
-var _canPlayMp4 = document.createElement('video').canPlayType('video/mp4');
 var _config = {
-    gui: {
-        width : 630,
-        container : 'LdtPlayer',
-        default_options: {
-            metadata: _metadata
+    width : 630,
+    container : 'LdtPlayer',
+    default_options: {
+        metadata: _metadata
+    },
+    css : '../metadataplayer/LdtPlayer-core.css',
+    widgets: [
+        {
+            type: "MashupPlayer",
+            url_transform: url_transform_func
+        },
+        { type: "Slider" },
+        { type: "Controller" },
+        {
+            type: "Segments",
+            annotation_type: false
         },
-        css : '../metadataplayer/LdtPlayer-core.css',
-        widgets: [
-            { type: "Slider" },
-            { type: "Controller" },
-            {
-                type: "Segments",
-                annotation_type: false
-            },
-            { type: "Arrow" },
-            {
-                type: "Annotation",
-                annotation_type: false
-            },
-            {
-                type: "Tagger",
-                api_endpoint: "../post-test.php",
-                tags: ["actif","amour","bonheur","captif","charité","désir","dieu","doute","famille","idéal","internationale","passif","patrie","peur","politique","président","spleen","travail"]
-            },
-            {
-                type: "MediaList",
-                container: "mediaList"
-            },
-            {
-                type: "AnnotationsList",
-                container: "annotationList",
-                //ajax_url: "http://ldt.iri.centrepompidou.fr/ldtplatform/api/ldt/segments/{{media}}/{{begin}}/{{end}}?callback=?",
-                //ajax_granularity: 30000,
-                //limit_count: 3
-            },
-            { type: "Mediafragment" }
-        ]
-    },
-    player:{
-        type:'mashup-html',
-        url_transform: function(_url) {
-            var _transformed = _url.replace(/^rtmp:\/\/media.iri.centrepompidou.fr\/ddc_player\/(mp4:)?video\//i,"http://media.iri.centrepompidou.fr/video/");
-            if (!/\.mp4$/.test(_transformed)) {
-                _transformed += '.mp4';
-            }
-            if (_canPlayMp4 == "") {
-                _transformed = _transformed.replace(/\.mp4$/i,'.ogv');
-            }
-            return _transformed;
+        {
+            type: "Annotation",
+            annotation_type: false
+        },
+        {
+            type: "Tagger",
+            api_endpoint: "../post-test.php",
+            tags: ["actif","amour","bonheur","captif","charité","désir","dieu","doute","famille","idéal","internationale","passif","patrie","peur","politique","président","spleen","travail"]
         },
-        height: 350,
-        width: 630,
-        metadata: _metadata
-    }
+        {
+            type: "MediaList",
+            container: "mediaList"
+        },
+        {
+            type: "AnnotationsList",
+            container: "annotationList",
+            //ajax_url: "http://ldt.iri.centrepompidou.fr/ldtplatform/api/ldt/segments/{{media}}/{{begin}}/{{end}}?callback=?",
+            //ajax_granularity: 30000,
+            //limit_count: 3
+        },
+        { type: "Mediafragment" }
+    ]
 };
 
 _myPlayer = new IriSP.Metadataplayer(_config);
--- a/metadataplayer/mashup/player-local-html.htm	Fri Aug 31 17:43:31 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,104 +0,0 @@
-<!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 (ne fonctionne pas avec Firefox)</h2>
-                <hr />
-                <div id="LdtPlayer"></div>
-            </div>
-            <div class="coldroite">
-                <div id="mediaList"></div>
-                <h2>Annotations</h2>
-                <hr />
-                <div id="annotationList"></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: 'bab_files/mashup-local-html.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: "AnnotationsList",
-                container: "annotationList",
-                ajax_url: "http://ldt.iri.centrepompidou.fr/ldtplatform/api/ldt/segments/{{media}}/{{begin}}/{{end}}?callback=?",
-                ajax_granularity: 30000,
-                limit_count: 3
-            },
-            { type: "Mediafragment" }
-        ]
-    },
-    player:{
-        type:'mashup-html',
-        height: 350,
-        width: 630,
-        metadata: _metadata
-    }
-};
-
-_myPlayer = new IriSP.Metadataplayer(_config);
-
-    </script>
-    </body>
-</html>
\ No newline at end of file
--- a/metadataplayer/mashup/player-local.htm	Fri Aug 31 17:43:31 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,109 +0,0 @@
-<!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>
-                <h2>Annotations</h2>
-                <hr />
-                <div id="annotationList"></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: 'bab_files/mashup.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: "AnnotationsList",
-                container: "annotationList",
-                ajax_url: "http://ldt.iri.centrepompidou.fr/ldtplatform/api/ldt/segments/{{media}}/{{begin}}/{{end}}?callback=?",
-                ajax_granularity: 30000,
-                limit_count: 3
-            },
-            { type: "Mediafragment" }
-        ]
-    },
-    player:{
-        type:'mashup',
-        live: true, 
-        height: 350,
-        width: 630, 
-        provider: "rtmp",
-        mashup_swf : "bab_files/player_bab_ldt.swf",
-        mashup_xml : "bab_files/mashup-local.xml",
-        autostart: true,
-        metadata: _metadata
-    }
-};
-
-_myPlayer = new IriSP.Metadataplayer(_config);
-
-    </script>
-    </body>
-</html>
\ No newline at end of file
--- a/metadataplayer/mashup/player.htm	Fri Aug 31 17:43:31 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,110 +0,0 @@
-<!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>
-                <h2>Annotations</h2>
-                <hr />
-                <div id="annotationList"></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: 'bab_files/mashup.json',
-//    url: 'http://ldt.iri.centrepompidou.fr/ldtplatform/ldt/cljson/id/b2754186-a0c9-11e0-b8bd-00145ea49a02?callback=?',
-    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: "AnnotationsList",
-                container: "annotationList",
-                ajax_url: "http://ldt.iri.centrepompidou.fr/ldtplatform/api/ldt/segments/{{media}}/{{begin}}/{{end}}?callback=?",
-                ajax_granularity: 30000,
-                limit_count: 3
-            },
-            { type: "Mediafragment" }
-        ]
-    },
-    player:{
-        type:'mashup',
-        live: true, 
-        height: 350,
-        width: 630, 
-        provider: "rtmp",
-        mashup_swf : "bab_files/player_bab_ldt.swf",
-        mashup_xml : "bab_files/mashup.xml",
-        autostart: true,
-        metadata: _metadata
-    }
-};
-
-_myPlayer = new IriSP.Metadataplayer(_config);
-
-    </script>
-    </body>
-</html>
\ No newline at end of file
--- a/metadataplayer/metadataplayer/Annotation.js	Fri Aug 31 17:43:31 2012 +0200
+++ b/metadataplayer/metadataplayer/Annotation.js	Thu Sep 27 12:23:41 2012 +0200
@@ -27,7 +27,8 @@
 }
 
 IriSP.Widgets.Annotation.prototype.template =
-    '<div class="Ldt-Annotation-Widget {{#show_top_border}}Ldt-Annotation-ShowTop{{/show_top_border}}">'
+    '{{#show_arrow}}<div class="Ldt-Annotation-Arrow"></div>{{/show_arrow}}'
+    + '<div class="Ldt-Annotation-Widget {{^show_arrow}}Ldt-Annotation-ShowTop{{/show_arrow}}">'
     + '<div class="Ldt-Annotation-Inner Ldt-Annotation-Empty{{#start_minimized}} Ldt-Annotation-Minimized{{/start_minimized}}">'
     + '<div class="Ldt-Annotation-HiddenWhenEmpty Ldt-Annotation-MaxMinButton"></div>'
     + '<div class="Ldt-Annotation-Social Ldt-Annotation-HiddenWhenMinimized Ldt-Annotation-HiddenWhenEmpty"></div>'
@@ -43,104 +44,112 @@
 
 IriSP.Widgets.Annotation.prototype.defaults = {
     annotation_type : "chap",
-    start_minimized: true,
-    show_top_border : false,
-    site_name : "Lignes de Temps"
+    start_minimized: false,
+    show_arrow : true,
+    site_name : "Lignes de Temps",
+    search_on_tag_click: true
 }
 
 IriSP.Widgets.Annotation.prototype.draw = function() {
+    
+    var _this = this;
+    
+    function timeupdate(_time) {
+        var _list = _this.getWidgetAnnotationsAtTime();
+        if (!_list.length) {
+            _this.$.find(".Ldt-Annotation-Inner").addClass("Ldt-Annotation-Empty");
+            if (_this.arrow) {
+                _this.arrow.moveToTime(_time);
+            }
+            _this.bounds = [ _time, _time ];
+            _this.sendBounds();
+        }
+    }
+    
+    function drawAnnotation(_annotation) {
+        var _url = (typeof _annotation.url !== "undefined" 
+                ? _annotation.url
+                : (document.location.href.replace(/#.*$/,'') + '#id='  + _annotation.id)),
+            _text = _this.l10n.watching + _annotation.title + (_this.site_name ? _this.l10n.on_site + _this.site_name : ''),
+            _tags = _annotation.getTags(),
+            _tagblock = _this.$.find(".Ldt-Annotation-Tags");
+        if (_tags.length) {
+            _this.$.find(".Ldt-Annotation-Tags-Block").removeClass("Ldt-Annotation-EmptyBlock");
+            _tags.forEach(function(_tag) {
+                var _trimmedTitle =  _tag.title.replace(/(^\s+|\s+$)/g,'');
+                if (_trimmedTitle) {
+                    var _el = IriSP.jQuery('<li class="Ldt-Annotation-TagLabel"></li>').append(IriSP.jQuery('<span>').text(_trimmedTitle));
+                    _el.click(function() {
+                        if (_this.search_on_tag_click) {
+                            _this.player.trigger("search.triggeredSearch",_trimmedTitle);
+                        }
+                        _tag.trigger("click");
+                    });
+                    _tagblock.append(_el);
+                }
+            });
+        } else {
+            _this.$.find(".Ldt-Annotation-Tags-Block").addClass("Ldt-Annotation-EmptyBlock");
+        }
+        _this.$.find(".Ldt-Annotation-Title").html(_annotation.title);
+        var _desc = _annotation.description.replace(/(^\s+|\s+$)/g,'');
+        if (_desc) {
+            _this.$.find(".Ldt-Annotation-Description-Block").removeClass("Ldt-Annotation-EmptyBlock");
+            _this.$.find(".Ldt-Annotation-Description").html(_desc);
+        } else {
+            _this.$.find(".Ldt-Annotation-Description-Block").addClass("Ldt-Annotation-EmptyBlock");
+        }
+        _this.$.find(".Ldt-Annotation-Begin").html(_annotation.begin.toString());
+        _this.$.find(".Ldt-Annotation-End").html(_annotation.end.toString());
+        if (_annotation.elementType === "mashedAnnotation") {
+            _this.$.find('.Ldt-Annotation-Inner').addClass("Ldt-Annotation-isMashup");
+            _this.$.find(".Ldt-Annotation-MashupMedia").html(_annotation.getMedia().title);
+            _this.$.find(".Ldt-Annotation-MashupBegin").html(_annotation.annotation.begin.toString());
+            _this.$.find(".Ldt-Annotation-MashupEnd").html(_annotation.annotation.end.toString());
+        } else {
+            _this.$.find('.Ldt-Annotation-Inner').removeClass("Ldt-Annotation-isMashup");
+        }
+        if (typeof _this.socialWidget !== "undefined") {
+            _this.socialWidget.updateUrls(_url, _text);
+        } else {
+            setTimeout(function() {
+                if (typeof _this.socialWidget !== "undefined") {
+                    _this.socialWidget.updateUrls(_url, _text);
+                }
+            },800);
+        }
+        _this.$.find(".Ldt-Annotation-Inner").removeClass("Ldt-Annotation-Empty");
+        _this.bounds = [ _annotation.begin, _annotation.end ];
+        if (_this.arrow) {
+            _this.arrow.moveToTime((_annotation.begin + _annotation.end)/2);
+        }
+        _this.sendBounds();
+    }
+    
     this.renderTemplate();
-    this.insertSubwidget(this.$.find(".Ldt-Annotation-Social"), "socialWidget", { type: "Social" });
-    this.bindPopcorn("timeupdate","onTimeupdate");
-    this.bindPopcorn("IriSP.Annotation.hide","hide");
-    this.bindPopcorn("IriSP.Annotation.show","show");
-    this.bindPopcorn("IriSP.Annotation.minimize","minimize");
-    this.bindPopcorn("IriSP.Annotation.maximize","maximize");
-    this.bindPopcorn("IriSP.Annotation.getBounds","sendBounds");
+    this.insertSubwidget(this.$.find(".Ldt-Annotation-Social"), { type: "Social" }, "socialWidget");
+    this.insertSubwidget(this.$.find(".Ldt-Annotation-Arrow"), { type: "Arrow" }, "arrow");
+    this.onMediaEvent("timeupdate",timeupdate);
+    this.onMdpEvent("Annotation.hide","hide");
+    this.onMdpEvent("Annotation.show","show");
+    this.onMdpEvent("Annotation.minimize","minimize");
+    this.onMdpEvent("Annotation.maximize","maximize");
+    this.onMdpEvent("Annotation.getBounds","sendBounds");
     this.$.find(".Ldt-Annotation-MaxMinButton").click(this.functionWrapper("toggleSize"));
-    this.onTimeupdate();
-}
-
-IriSP.Widgets.Annotation.prototype.onTimeupdate = function() {
-    var _time = Math.floor(this.player.popcorn.currentTime() * 1000),
-        _list = this.getWidgetAnnotationsAtTime();
-    if (_list.length) {
-        if (_list[0].id !== this.lastAnnotation) {
-            this.drawAnnotation(_list[0]);
-            this.bounds = [ _list[0].begin.valueOf(), _list[0].end.valueOf() ];
-        }
-        this.player.popcorn.trigger("IriSP.Arrow.updatePosition",{widget: this.type, time: ( _list[0].begin + _list[0].end ) / 2});
-    }
-    else {
-        this.lastAnnotation = false;
-        this.$.find(".Ldt-Annotation-Inner").addClass("Ldt-Annotation-Empty");
-        this.player.popcorn.trigger("IriSP.Arrow.updatePosition",{widget: this.type, time: _time});
-        this.bounds = [ _time, _time ];
-    }
-    this.sendBounds();
+    this.getWidgetAnnotations().forEach(function(_a) {
+        _a.on("enter", function() {
+            drawAnnotation(_a)
+        });
+    });
 }
 
 IriSP.Widgets.Annotation.prototype.sendBounds = function() {
-    this.player.popcorn.trigger("IriSP.Annotation.boundsChanged",this.bounds);
+    this.player.trigger("Annotation.boundsChanged",this.bounds);
 }
 
 IriSP.Widgets.Annotation.prototype.drawAnnotation = function(_annotation) {
     this.lastAnnotation = _annotation.id;
-    var _url = (typeof _annotation.url !== "undefined" 
-            ? _annotation.url
-            : (document.location.href.replace(/#.*$/,'') + '#id='  + _annotation.id));
-    var _text = this.l10n.watching + _annotation.title + (this.site_name ? this.l10n.on_site + this.site_name : '');
-    var _tags = _annotation.getTagTexts();
-    if (_tags.length) {
-        var _html = IriSP._(_tags).map(function(_tag) {
-            return '<li class="Ldt-Annotation-TagLabel"><span>' + _tag + '</span></li>';
-        }).join("");
-        this.$.find(".Ldt-Annotation-Tags").html(_html);
-        this.$.find(".Ldt-Annotation-Tags-Block").removeClass("Ldt-Annotation-EmptyBlock");
-        
-        /* Correct the empty tag bug */
-        this.$.find('.Ldt-Annotation-TagLabel').each(function() {
-            var _el = IriSP.jQuery(this);
-            if (!_el.text().replace(/(^\s+|\s+$)/g,'')) {
-                _el.detach();
-            }
-        });
-    
-        this.$.find('.Ldt-Annotation-TagLabel').click(function() {
-            _this.player.popcorn.trigger("IriSP.search.triggeredSearch", IriSP.jQuery(this).text().replace(/(^\s+|\s+$)/g,''));
-        });
-    } else {
-        this.$.find(".Ldt-Annotation-Tags-Block").addClass("Ldt-Annotation-EmptyBlock");
-    }
-    this.$.find(".Ldt-Annotation-Title").html(_annotation.title);
-    var _desc = _annotation.description.replace(/(^\s+|\s+$)/g,'');
-    if (_desc) {
-        this.$.find(".Ldt-Annotation-Description-Block").removeClass("Ldt-Annotation-EmptyBlock");
-        this.$.find(".Ldt-Annotation-Description").html(_desc);
-    } else {
-        this.$.find(".Ldt-Annotation-Description-Block").addClass("Ldt-Annotation-EmptyBlock");
-    }
-    this.$.find(".Ldt-Annotation-Begin").html(_annotation.begin.toString());
-    this.$.find(".Ldt-Annotation-End").html(_annotation.end.toString());
-    if (_annotation.elementType === "mashedAnnotation") {
-        this.$.find('.Ldt-Annotation-Inner').addClass("Ldt-Annotation-isMashup");
-        this.$.find(".Ldt-Annotation-MashupMedia").html(_annotation.getMedia().title);
-        this.$.find(".Ldt-Annotation-MashupBegin").html(_annotation.annotation.begin.toString());
-        this.$.find(".Ldt-Annotation-MashupEnd").html(_annotation.annotation.end.toString());
-    } else {
-        this.$.find('.Ldt-Annotation-Inner').removeClass("Ldt-Annotation-isMashup");
-    }
-    if (typeof this.socialWidget !== "undefined") {
-        this.socialWidget.updateUrls(_url, _text);
-    } else {
-        var _this = this;
-        setTimeout(function() {
-            if (typeof _this.socialWidget !== "undefined") {
-                _this.socialWidget.updateUrls(_url, _text);
-            }
-        },800);
-    }
-    this.$.find(".Ldt-Annotation-Inner").removeClass("Ldt-Annotation-Empty");
+
 }
 
 IriSP.Widgets.Annotation.prototype.hide = function() {
--- a/metadataplayer/metadataplayer/AnnotationsList.css	Fri Aug 31 17:43:31 2012 +0200
+++ b/metadataplayer/metadataplayer/AnnotationsList.css	Thu Sep 27 12:23:41 2012 +0200
@@ -8,6 +8,11 @@
 .Ldt-AnnotationsListWidget a {
     text-decoration: none;
 }
+
+.Ldt-AnnotationsList-Audio {
+    width: 1px; height: 1px;
+}
+
 ul.Ldt-AnnotationsList-ul {
     list-style: none;
     padding: 2px;
@@ -20,8 +25,8 @@
     padding: 2px 0;
     min-height: 60px;
 }
-.Ldt-AnnotationsList-li:hover {
-    background: url(img/pinstripe-grey.png) !important;
+.Ldt-AnnotationsList-li.selected {
+    background-image: url(img/pinstripe-grey.png);
 }
 .Ldt-AnnotationsList-highlight {
     background: #F7268E;
@@ -84,4 +89,14 @@
 
 .Ldt-AnnotationsList-Tag-Li:hover span {
     background-position: right -23px;
+}
+
+.Ldt-AnnotationsList-Play {
+    width: 125px; height: 20px; margin: 2px 0 2px 82px; text-align: center;
+    padding: 3px 5px 0 20px; font-size: 12px; cursor: pointer;
+    background: url(img/voiceannotation.png); color: #333333;
+}
+
+.Ldt-AnnotationsList-Play:hover {
+    background-position: 0 bottom;
 }
\ No newline at end of file
--- a/metadataplayer/metadataplayer/AnnotationsList.js	Fri Aug 31 17:43:31 2012 +0200
+++ b/metadataplayer/metadataplayer/AnnotationsList.js	Thu Sep 27 12:23:41 2012 +0200
@@ -6,7 +6,6 @@
     this.throttledRefresh = IriSP._.throttle(function() {
         _this.refresh(false);
     }, 1500);
-    this.mashupMode = (this.source.currentMedia.elementType === "mashup");
 };
 
 IriSP.Widgets.AnnotationsList.prototype = new IriSP.Widgets.Widget();
@@ -18,7 +17,7 @@
     ajax_url : false,
     /* number of milliseconds before/after the current timecode when calling the segment API
      */
-    ajax_granularity : 300000, 
+    ajax_granularity : 600000, 
     default_thumbnail : "",
     /* URL when the annotation is not in the current project,
      * e.g. http://ldt.iri.centrepompidou.fr/ldtplatform/ldt/front/player/{{media}}/{{project}}/{{annotationType}}#id={{annotation}}
@@ -26,8 +25,9 @@
     foreign_url : "",
     annotation_type : false,
     refresh_interval : 0,
-    limit_count : 10,
+    limit_count : 20,
     newest_first : false,
+    show_audio: true,
     polemics : [{
         keyword: "++",
         background_color: "#c9ecc6"
@@ -43,11 +43,26 @@
     }]
 };
 
+IriSP.Widgets.AnnotationsList.prototype.messages = {
+    en: {
+        voice_annotation: "Voice Annotation",
+        now_playing: "Now playing..."
+    },
+    fr: {
+        voice_annotation: "Annotation Vocale",
+        now_playing: "Lecture en cours..."
+    }
+}
+
 IriSP.Widgets.AnnotationsList.prototype.template =
     '<div class="Ldt-AnnotationsListWidget">'
+    + '{{#show_audio}}<div class="Ldt-AnnotationsList-Audio"></div>{{/show_audio}}'
     + '<ul class="Ldt-AnnotationsList-ul">'
-    + '{{#annotations}}'
-    + '<li class="Ldt-AnnotationsList-li Ldt-TraceMe" trace-info="annotation-id:{{id}}, media-id:{{media_id}}" style="{{specific_style}}">'
+    + '</ul>'
+    + '</div>';
+
+IriSP.Widgets.AnnotationsList.prototype.annotationTemplate = 
+    '<li class="Ldt-AnnotationsList-li Ldt-TraceMe" trace-info="annotation-id:{{id}}, media-id:{{media_id}}" style="{{specific_style}}">'
     + '<div class="Ldt-AnnotationsList-ThumbContainer">'
     + '<a href="{{url}}">'
     + '<img class="Ldt-AnnotationsList-Thumbnail" src="{{thumbnail}}" />'
@@ -69,19 +84,17 @@
     + '{{/tags}}'
     + '</ul>'
     + '{{/tags.length}}'
-    + '</li>'
-    + '{{/annotations}}'
-    + '</ul>'
-    + '</div>';
+    + '{{#audio}}<div class="Ldt-AnnotationsList-Play" data-audio={{audio}}>{{l10n.voice_annotation}}</div>{{/audio}}'
+    + '</li>';
 
 IriSP.Widgets.AnnotationsList.prototype.onSearch = function(searchString) {
     this.searchString = typeof searchString !== "undefined" ? searchString : '';
     var _n = this.refresh(true);
     if (this.searchString) {
         if (_n) {
-            this.player.popcorn.trigger("IriSP.search.matchFound");
+            this.player.trigger("search.matchFound");
         } else {
-            this.player.popcorn.trigger("IriSP.search.noMatchFound");
+            this.player.trigger("search.noMatchFound");
         }
     }
 }
@@ -89,13 +102,9 @@
 //obj.url = this.project_url + "/" + media + "/" + annotations[i].meta.project + "/" + annotations[i].meta["id-ref"] + '#id=' + annotations[i].id;
 
 IriSP.Widgets.AnnotationsList.prototype.ajaxSource = function() {
-    var _currentTime = this.player.popcorn.currentTime(),
-        _duration = this.source.getDuration();
-    if (typeof _currentTime == "undefined") {
-        _currentTime = 0;
-    }
+    var _currentTime = this.media.getCurrentTime(),
+        _duration = this.media.duration;
     this.lastAjaxQuery = _currentTime;
-    _currentTime = Math.floor(1000 * _currentTime);
     var _url = Mustache.to_html(this.ajax_url, {
         media : this.source.currentMedia.id,
         begin : Math.max(0, _currentTime - this.ajax_granularity),
@@ -107,11 +116,8 @@
 }
 
 IriSP.Widgets.AnnotationsList.prototype.ajaxMashup = function() {
-    var _currentTime = this.player.popcorn.currentTime();
-    if (typeof _currentTime == "undefined") {
-        _currentTime = 0;
-    }
-    var _currentAnnotation = this.source.currentMedia.getAnnotationAtTime(_currentTime * 1000);
+    var _currentTime = this.media.getCurrentTime();
+    var _currentAnnotation = this.source.currentMedia.getAnnotationAtTime(_currentTime);
     if (typeof _currentAnnotation !== "undefined" && _currentAnnotation.id !== this.lastMashupAnnotation) {
         this.lastMashupAnnotation = _currentAnnotation.id;
         var _currentMedia = _currentAnnotation.getMedia(),
@@ -132,15 +138,12 @@
         return 0;
     }
     var _this = this,
-        _currentTime = this.player.popcorn.currentTime();
-    if (typeof _currentTime == "undefined") {
-        _currentTime = 0;
-    }
+        _currentTime = this.media.getCurrentTime();
     var _list = this.annotation_type ? this.currentSource.getAnnotationsByTypeTitle(this.annotation_type) : this.currentSource.getAnnotations();
     if (this.mashupMode) {
-        var _currentAnnotation = this.source.currentMedia.getAnnotationAtTime(_currentTime * 1000);
+        var _currentAnnotation = this.source.currentMedia.getAnnotationAtTime(_currentTime);
         if (typeof _currentAnnotation !== "undefined") {
-            _currentTime = _currentTime - _currentAnnotation.begin.getSeconds() + _currentAnnotation.annotation.begin.getSeconds();
+            _currentTime = _currentTime - _currentAnnotation.begin + _currentAnnotation.annotation.begin;
             var _mediaId = _currentAnnotation.getMedia().id;
             _list = _list.filter(function(_annotation) {
                 return _annotation.getMedia().id === _mediaId;
@@ -151,8 +154,9 @@
         _list = _list.searchByTextFields(this.searchString);
     }
     if (this.limit_count) {
+        /* Get the n annotations closest to current timecode */
         _list = _list.sortBy(function(_annotation) {
-            return Math.abs(_annotation.begin.getSeconds() - _currentTime);
+            return Math.abs((_annotation.begin + _annotation.end) / 2 - _currentTime);
         }).slice(0, this.limit_count)
     }
     if (this.newest_first) {
@@ -170,61 +174,74 @@
     if (_forceRedraw || !IriSP._.isEqual(_ids, this.lastIds)) {
         /* This part only gets executed if the list needs updating */
         this.lastIds = _ids;
-        var _data = _list.map(function(_annotation) {
-                    var _url = (
-                        ( typeof _annotation.url !== "undefined" && _annotation.url)
-                        ? _annotation.url
-                        : (
-                            ( typeof _this.source.projectId !== "undefined" && typeof _annotation.project !== "undefined" && _annotation.project && _this.source.projectId !== _annotation.project )
-                            ? Mustache.to_html(
-                                _this.foreign_url,
-                                {
-                                    project : _annotation.project,
-                                    media : _annotation.media.id,
-                                    annotation : _annotation.id,
-                                    annotationType : _annotation.annotationType.id
-                                }
-                            )
-                            : '#id=' + _annotation.id
-                            )
-                    );
-                    var _title = (_annotation.title || "").replace(_annotation.description,''),
-                        _description = _annotation.description;
-                    if (!_annotation.title) {
-                        _title = _annotation.creator;
-                    }
-                    if (!_annotation.description && _annotation.creator) {
-                        _description = _annotation.title;
-                        _title = _annotation.creator;
-                    }
-                    var _bgcolor;
-                    IriSP._(_this.polemics).each(function(_polemic) {
-                        var _rgxp = IriSP.Model.regexpFromTextOrArray(_polemic.keyword, true);
-                        if (_rgxp.test(_title + " " + _description)) {
-                            _bgcolor = _polemic.background_color;
+        this.list_$.html("");
+        _list.forEach(function(_annotation) {
+            var _url = (
+                ( typeof _annotation.url !== "undefined" && _annotation.url)
+                ? _annotation.url
+                : (
+                    ( typeof _this.source.projectId !== "undefined" && typeof _annotation.project !== "undefined" && _annotation.project && _this.source.projectId !== _annotation.project )
+                    ? Mustache.to_html(
+                        _this.foreign_url,
+                        {
+                            project : _annotation.project,
+                            media : _annotation.media.id,
+                            annotation : _annotation.id,
+                            annotationType : _annotation.annotationType.id
                         }
-                    });
-                    var _res = {
-                        id : _annotation.id,
-                        media_id : _annotation.getMedia().id,
-                        title : _title,
-                        description : _description,
-                        begin : _annotation.begin.toString(),
-                        end : _annotation.end.toString(),
-                        thumbnail : typeof _annotation.thumbnail !== "undefined" && _annotation.thumbnail ? _annotation.thumbnail : _this.default_thumbnail,
-                        url : _url,
-                        tags : _annotation.getTagTexts(),
-                        specific_style : (typeof _bgcolor !== "undefined" ? "background: " + _bgcolor : "")
-                    }
-                    return _res;
-            }),
-            _html = Mustache.to_html(
-                this.template,
-                {
-                    annotations : _data
-                });
-    
-        this.$.html(_html);
+                    )
+                    : '#id=' + _annotation.id
+                    )
+            );
+            var _title = (_annotation.title || "").replace(_annotation.description,''),
+                _description = _annotation.description;
+            if (!_annotation.title) {
+                _title = _annotation.creator;
+            }
+            if (!_annotation.description && _annotation.creator) {
+                _description = _annotation.title;
+                _title = _annotation.creator;
+            }
+            var _bgcolor;
+            IriSP._(_this.polemics).each(function(_polemic) {
+                var _rgxp = IriSP.Model.regexpFromTextOrArray(_polemic.keyword, true);
+                if (_rgxp.test(_title + " " + _description)) {
+                    _bgcolor = _polemic.background_color;
+                }
+            });
+            var _data = {
+                id : _annotation.id,
+                media_id : _annotation.getMedia().id,
+                title : _title,
+                description : _description,
+                begin : _annotation.begin.toString(),
+                end : _annotation.end.toString(),
+                thumbnail : typeof _annotation.thumbnail !== "undefined" && _annotation.thumbnail ? _annotation.thumbnail : _this.default_thumbnail,
+                url : _url,
+                tags : _annotation.getTagTexts(),
+                specific_style : (typeof _bgcolor !== "undefined" ? "background-color: " + _bgcolor : ""),
+                audio : (_this.show_audio && _annotation.audio && _annotation.audio.href && _annotation.audio.href != "null" ? _annotation.audio.href : undefined),
+                l10n: _this.l10n
+            };
+            var _html = Mustache.to_html(_this.annotationTemplate, _data);
+            var _el = IriSP.jQuery(_html);
+            _el.mouseover(function() {
+                    _annotation.trigger("select");
+                })
+                .mouseout(function() {
+                    _annotation.trigger("unselect");
+                })
+                .appendTo(_this.list_$);
+            _annotation.on("select", function() {
+                _this.annotations_$.removeClass("selected");
+                _el.addClass("selected");
+            });
+            _annotation.on("unselect", function() {
+                _this.annotations_$.removeClass("selected");
+            });;
+        });
+        
+        this.annotations_$ = this.$.find('.Ldt-AnnotationsList-li');
         
         /* Correct the empty tag bug */
         this.$.find('.Ldt-AnnotationsList-Tag-Li').each(function() {
@@ -235,8 +252,21 @@
         });
     
         this.$.find('.Ldt-AnnotationsList-Tag-Li').click(function() {
-            _this.player.popcorn.trigger("IriSP.search.triggeredSearch", IriSP.jQuery(this).text().replace(/(^\s+|\s+$)/g,''));
-        })
+            _this.player.trigger("search.triggeredSearch", IriSP.jQuery(this).text().replace(/(^\s+|\s+$)/g,''));
+        });
+        
+        this.$.find(".Ldt-AnnotationsList-Play").click(function() {
+            var _el = IriSP.jQuery(this),
+                _audiofile = _el.attr("data-audio").replace(_this.rtmp_streamer,"");
+            _el.text(_this.l10n.now_playing);
+            _this.jwplayer.load({
+                file: _audiofile,
+                streamer: _this.rtmp_streamer
+            });
+            _this.jwplayer.play(true);
+            _this.media.pause();
+            _this.jw_paused_media = true;
+        });
         
         if(this.searchString) {
             var _searchRe = IriSP.Model.regexpFromTextOrArray(this.searchString);
@@ -251,7 +281,7 @@
         if (this.mashupMode) {
             this.ajaxMashup();
         } else {
-            if (Math.abs(_currentTime - this.lastAjaxQuery) > (this.ajax_granularity / 2000)) {
+            if (Math.abs(_currentTime - this.lastAjaxQuery) > (this.ajax_granularity)) {
                 this.ajaxSource();
             }
         }
@@ -261,13 +291,51 @@
 
 IriSP.Widgets.AnnotationsList.prototype.draw = function() {
     
-    this.bindPopcorn("IriSP.search", "onSearch");
-    this.bindPopcorn("IriSP.search.closed", "onSearch");
-    this.bindPopcorn("IriSP.search.cleared", "onSearch");
-    this.bindPopcorn("IriSP.AnnotationsList.refresh","refresh");
+    this.mashupMode = (this.media.elementType === "mashup");
+    
+    this.renderTemplate();
     
     var _this = this;
     
+    if (this.show_audio) {
+        var _tmpId = "jwplayer-" + IriSP.Model.getUID();
+        this.$.find(".Ldt-AnnotationsList-Audio").attr("id", _tmpId);
+        this.jwplayer = jwplayer(_tmpId);
+        this.jwplayer.setup({
+            flashplayer: IriSP.getLib("jwPlayerSWF"),
+            width: 1,
+            height: 1,
+            provider: "rtmp",
+            events: {
+                onIdle: function() {
+                    if (_this.jw_paused_media) {
+                        _this.jw_paused_media = false;
+                        _this.media.play();
+                    }
+                    _this.$.find(".Ldt-AnnotationsList-Play").text(_this.l10n.voice_annotation)
+                }
+            }
+        });
+        this.jw_paused_media = false;
+    }
+    
+    this.list_$ = this.$.find(".Ldt-AnnotationsList-ul");
+    
+    
+    this.onMdpEvent("search", "onSearch");
+    this.onMdpEvent("search.closed", "onSearch");
+    this.onMdpEvent("search.cleared", "onSearch");
+    this.onMdpEvent("AnnotationsList.refresh", function() {
+        if (_this.ajax_url) {
+            if (_this.mashupMode) {
+                _this.ajaxMashup();
+            } else {
+                _this.ajaxSource();
+            }
+        }
+        _this.throttledRefresh();
+    });
+    
     if (this.ajax_url) {
         if (this.mashupMode) {
             this.ajaxMashup();
@@ -284,14 +352,14 @@
         }, this.refresh_interval);
     }
     
+    this.onMdpEvent("createAnnotationWidget.addedAnnotation");
     var _events = [
-        "IriSP.createAnnotationWidget.addedAnnotation",
         "timeupdate",
         "seeked",
         "loadedmetadata"
     ];
     for (var _i = 0; _i < _events.length; _i++) {
-        this.player.popcorn.listen(_events[_i], this.throttledRefresh);
+        this.onMediaEvent(_events[_i], this.throttledRefresh);
     }
     
     this.throttledRefresh();
--- a/metadataplayer/metadataplayer/Arrow.js	Fri Aug 31 17:43:31 2012 +0200
+++ b/metadataplayer/metadataplayer/Arrow.js	Thu Sep 27 12:23:41 2012 +0200
@@ -6,25 +6,25 @@
 IriSP.Widgets.Arrow.prototype = new IriSP.Widgets.Widget();
 
 IriSP.Widgets.Arrow.prototype.defaults = {
-    arrow_height : 16,
-    arrow_width : 24,
+    arrow_height : 12,
+    arrow_width : 20,
     base_height : 0,
     base_curve : 0,
     fill_url: IriSP.widgetsDir + '/img/pinstripe.png',
     fill_color: "#ffffff", //Gradients can be used, e.g. "90-#000-#fff" for vertical white-to-black
     stroke_color: "#b7b7b7",
     stroke_width: 1.5,
-    animation_speed: 20,
-    pilot_widget: "Annotation"
+    animation_speed: 20
 }
 
 IriSP.Widgets.Arrow.prototype.draw = function() {
     this.height = this.arrow_height + this.base_height;
     this.$.addClass("Ldt-Arrow").css({
-        height: this.height + "px",
-        "margin-top": "1px"
+        height: (1+this.height) + "px",
+        "margin-top": "1px",
+        overflow: "hidden"
     });
-    this.paper = new Raphael(this.container, this.width, this.height );
+    this.paper = new Raphael(this.container, this.width, 1+this.height );
     window.myArrow = this;
     this.svgArrow = this.paper.path('M0,' + this.height + 'L' + this.width + ',' + this.height);
     this.svgArrow.attr({
@@ -32,14 +32,11 @@
         "stroke-width": this.stroke_width,
         fill: this.fill_url ? ( 'url(' + this.fill_url + ')' ) : this.fill_color
     });
-    this.moveTo(0);
-    this.bindPopcorn("IriSP.Arrow.updatePosition","onUpdatePosition");
-    this.bindPopcorn("IriSP.Arrow.takeover","onTakeover");
-    this.bindPopcorn("IriSP.Arrow.release","onRelease");
+    this.moveToX(0);
 }
 
 IriSP.Widgets.Arrow.prototype.drawAt = function(_x) {
-    _x = Math.floor(Math.max(0, Math.min(_x, this.width)));
+    _x = Math.max(0, Math.min(_x, this.width));
     var _d = 'M0,' + this.height
         + 'L0,' + Math.min( this.height, this.arrow_height + this.base_curve)
         + 'Q0,' + this.arrow_height
@@ -58,8 +55,8 @@
     });
 }
 
-IriSP.Widgets.Arrow.prototype.moveTo = function(_x) {
-    this.targetX = Math.floor(Math.max(0, Math.min(_x, this.width)));
+IriSP.Widgets.Arrow.prototype.moveToX = function(_x) {
+    this.targetX = Math.max(0, Math.min(_x, this.width));
     if (typeof this.animInterval === "undefined") {
         this.animInterval = window.setInterval(
             this.functionWrapper("increment"),
@@ -69,6 +66,10 @@
     this.increment();
 }
 
+IriSP.Widgets.Arrow.prototype.moveToTime = function(_t) {
+    this.moveToX(this.width * _t / this.media.duration);
+}
+
 IriSP.Widgets.Arrow.prototype.increment = function() {
     if (typeof this.currentX === "undefined") {
         this.currentX = this.targetX;
@@ -85,21 +86,3 @@
     }
     this.drawAt(this.currentX);
 }
-
-IriSP.Widgets.Arrow.prototype.onUpdatePosition = function(_param) {
-    if (_param.widget === this.current_pilot_widget) {
-        if (typeof _param.x !== "undefined") {
-            this.moveTo(_param.x);
-        } else {
-            this.moveTo(this.width * _param.time / this.source.getDuration());
-        }
-    }
-}
-
-IriSP.Widgets.Arrow.prototype.onTakeover = function(_widget) {
-    this.current_pilot_widget = _widget;
-}
-
-IriSP.Widgets.Arrow.prototype.onRelease = function(_widget) {
-    this.current_pilot_widget = this.pilot_widget;
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/metadataplayer/metadataplayer/AutoPlayer.js	Thu Sep 27 12:23:41 2012 +0200
@@ -0,0 +1,57 @@
+IriSP.Widgets.AutoPlayer = function(player, config) {
+    IriSP.Widgets.Widget.call(this, player, config);
+};
+
+IriSP.Widgets.AutoPlayer.prototype = new IriSP.Widgets.Widget();
+
+IriSP.Widgets.AutoPlayer.prototype.defaults = {
+    default_type: "JwpPlayer"
+}
+
+IriSP.Widgets.AutoPlayer.prototype.draw = function() {
+    
+    if (typeof this.video === "undefined") {
+        this.video = this.media.video;
+    }
+    
+    var _props = [ "live", "provider", "autostart", "streamer", "video", "height", "width", "url_transform" ],
+        _opts = {},
+        _types = [
+            {
+                regexp: /\.(ogg|ogv|webm)$/,
+                type: "PopcornPlayer"
+            },
+            {
+                regexp: /^(https?:\/\/)?(www\.)?youtube\.com/,
+                type: "PopcornPlayer"
+            },
+            {
+                regexp: /^(https?:\/\/)?(www\.)?vimeo\.com/,
+                type: "PopcornPlayer"
+            },
+            {
+                regexp: /^(https?:\/\/)?(www\.)?dailymotion\.com/,
+                type: "DailymotionPlayer"
+            }
+        ];
+    
+    for (var i = 0; i < _types.length; i++) {
+        if (_types[i].regexp.test(this.video)) {
+            _opts.type =  _types[i].type
+        }
+    }
+    
+    if (typeof _opts.type === "undefined") {
+        _opts.type = this.default_type
+    }
+    
+    for (var i = 0; i < _props.length; i++) {
+        if (typeof this[_props[i]] !== "undefined") {
+            _opts[_props[i]] = this[_props[i]];
+        }
+    }
+    
+
+    this.insertSubwidget(this.$, _opts);
+    
+}
\ No newline at end of file
--- a/metadataplayer/metadataplayer/Controller.css	Fri Aug 31 17:43:31 2012 +0200
+++ b/metadataplayer/metadataplayer/Controller.css	Thu Sep 27 12:23:41 2012 +0200
@@ -9,7 +9,7 @@
 }
 
 .Ldt-Ctrl-Left {
-  float:left;
+  float:left; width: 300px;
 }
 
 .Ldt-Ctrl-Right {
@@ -82,13 +82,12 @@
 }
 
 .Ldt-Ctrl-Search {
-  display: none;
-  width: 165px;
-  height: 25px;
-  border: 1px;
-  border-color: #CFCFCF;
-  float: left;
-  text-align: center;
+  width: 0; float: left; overflow: hidden;
+}
+
+input.Ldt-Ctrl-SearchInput {
+    width: 145px; height: 13px; margin: 2px; padding: 3px;
+    border: 1px solid #8080a0; border-radius: 3px; font-size: 13px;
 }
 
 .Ldt-Ctrl-Time {
--- a/metadataplayer/metadataplayer/Controller.js	Fri Aug 31 17:43:31 2012 +0200
+++ b/metadataplayer/metadataplayer/Controller.js	Thu Sep 27 12:23:41 2012 +0200
@@ -1,16 +1,16 @@
 /* Displays Play and Pause buttons, Search Button and Form, Volume Control */
 
 IriSP.Widgets.Controller = function(player, config) {
-  IriSP.Widgets.Widget.call(this, player, config);
-  
-  this._searchLastValue = "";
+    IriSP.Widgets.Widget.call(this, player, config);
+    this.lastSearchValue = "";
 };
 
 IriSP.Widgets.Controller.prototype = new IriSP.Widgets.Widget();
 
 IriSP.Widgets.Controller.prototype.defaults = {
     disable_annotate_btn: false,
-    disable_search_btn: false
+    disable_search_btn: false,
+    disable_ctrl_f: false
 }
 
 IriSP.Widgets.Controller.prototype.template =
@@ -87,25 +87,25 @@
     this.$volumeBar = this.$.find(".Ldt-Ctrl-Volume-Bar");
     
     // handle events
-    this.bindPopcorn("play","playButtonUpdater");
-    this.bindPopcorn("pause","playButtonUpdater");
-    this.bindPopcorn("volumechange","volumeUpdater");
-    this.bindPopcorn("timeupdate","timeDisplayUpdater");
-    this.bindPopcorn("loadedmetadata","timeDisplayUpdater");
-    this.bindPopcorn("loadedmetadata","volumeUpdater");
-    this.bindPopcorn("IriSP.search.matchFound","searchMatch");
-    this.bindPopcorn("IriSP.search.noMatchFound","searchNoMatch");
-    this.bindPopcorn("IriSP.search.triggeredSearch","triggeredSearch");
+    this.onMediaEvent("play","playButtonUpdater");
+    this.onMediaEvent("pause","playButtonUpdater");
+    this.onMediaEvent("volumechange","volumeUpdater");
+    this.onMediaEvent("timeupdate","timeDisplayUpdater");
+    this.onMediaEvent("loadedmetadata","volumeUpdater");
+    this.onMdpEvent("search.matchFound","searchMatch");
+    this.onMdpEvent("search.noMatchFound","searchNoMatch");
+    this.onMdpEvent("search.triggeredSearch","triggeredSearch");
+    this.onMdpEvent("search.cleared","hideSearchBlock");
     
     // handle clicks
     this.$playButton.click(this.functionWrapper("playHandler"));
     
     this.$.find(".Ldt-Ctrl-Annotate").click(function() {
-        _this.player.popcorn.trigger("IriSP.CreateAnnotation.toggle");
+        _this.player.trigger("CreateAnnotation.toggle");
     });
     this.$.find(".Ldt-Ctrl-SearchBtn").click(this.functionWrapper("searchButtonHandler"));
     
-    this.$searchInput.keyup(this.functionWrapper("searchHandler") );
+    this.$searchInput.keyup(this.functionWrapper("searchHandler"));
   
 	var _volctrl = this.$.find(".Ldt-Ctrl-Volume-Control");
     this.$.find('.Ldt-Ctrl-Sound')
@@ -121,13 +121,28 @@
     }).mouseout(function() {
         _volctrl.hide();
     });
-  
+    
+    // Handle CTRL-F
+    if (!this.disable_ctrl_f) {
+        var _fKey = "F".charCodeAt(0),
+            _lastCtrlFTime = 0;
+        IriSP.jQuery(document).keydown(function(_event) {
+            if (_event.keyCode === _fKey && (_event.ctrlKey || _event.metaKey)) {
+                var _time = IriSP.jQuery.now();
+                if (_time - _lastCtrlFTime > 2000) {
+                    _this.searchButtonHandler();
+                }
+                _lastCtrlFTime = _time;
+                return false;
+            }
+        });
+    }
     
     // Allow Volume Cursor Dragging
     this.$volumeBar.slider({
         slide: function(event, ui) {
             _this.$volumeBar.attr("title",_this.l10n.volume+': ' + ui.value + '%');
-            _this.player.popcorn.volume(ui.value / 100);
+            _this.media.setVolume(ui.value / 100);
         },
         stop: this.functionWrapper("volumeUpdater")
     });
@@ -135,33 +150,26 @@
     // trigger an IriSP.Player.MouseOver to the widgets that are interested (i.e : sliderWidget)
     this.$.hover(
         function() {
-            _this.player.popcorn.trigger("IriSP.Player.MouseOver");
+            _this.player.trigger("Player.MouseOver");
         }, 
         function() {
-            _this.player.popcorn.trigger("IriSP.Player.MouseOut");
+            _this.player.trigger("Player.MouseOut");
         });
+    
+    this.timeDisplayUpdater(new IriSP.Model.Time(0));
     /* some players - including jwplayer - save the state of the mute button between sessions */
-
+   //TODO: MOVE TO THE PLAYER/
     window.setTimeout(this.functionWrapper("volumeUpdater"), 1000);
    
 };
 
 /* Update the elasped time div */
-IriSP.Widgets.Controller.prototype.timeDisplayUpdater = function() {
-    var _curTime = this.player.popcorn.roundTime();
-    if (typeof this._previousSecond !== "undefined" && _curTime === this._previousSecond) {
-        return;
-    }
+IriSP.Widgets.Controller.prototype.timeDisplayUpdater = function(_time) {
   
     // we get it at each call because it may change.
-    var _totalTime = this.source.getDuration(),
-        _elapsedTime = new IriSP.Model.Time();
-        
-    _elapsedTime.setSeconds(_curTime);
-  
-    this.$.find(".Ldt-Ctrl-Time-Elapsed").html(_elapsedTime.toString());
+    var _totalTime = this.media.duration;
+    this.$.find(".Ldt-Ctrl-Time-Elapsed").html(_time.toString());
     this.$.find(".Ldt-Ctrl-Time-Total").html(_totalTime.toString());
-    this._previousSecond = _curTime;
 };
 
 /* update the icon of the button - separate function from playHandler
@@ -169,10 +177,7 @@
    the jwplayer window) we have to change the icon without playing/pausing
 */
 IriSP.Widgets.Controller.prototype.playButtonUpdater = function() {
-    
-    var status = this.player.popcorn.media.paused;
-  
-    if (status) {
+    if (this.media.getPaused()) {
     /* the background sprite is changed by adding/removing the correct classes */
         this.$playButton
             .attr("title", this.l10n.play)
@@ -188,23 +193,24 @@
 
 
 IriSP.Widgets.Controller.prototype.playHandler = function() {
-    
-    var status = this.player.popcorn.media.paused;
-  
-    if (status) {        
-        this.player.popcorn.play();   
+    if (this.media.getPaused()) {        
+        this.media.play();
     } else {
-        this.player.popcorn.pause();
+        this.media.pause();
     }  
 };
 
 IriSP.Widgets.Controller.prototype.muteHandler = function() {
-    this.player.popcorn.muted(!this.player.popcorn.muted());
+    if (this.media.getMuted()) {
+        this.media.unmute();
+    } else {
+        this.media.mute();
+    }
 };
 
 IriSP.Widgets.Controller.prototype.volumeUpdater = function() {
-    var _muted = this.player.popcorn.muted(),
-        _vol = this.player.popcorn.volume();
+    var _muted = this.media.getMuted(),
+        _vol = this.media.getVolume();
     if (_vol === false) {
         _vol = .5;
     }
@@ -221,7 +227,7 @@
 };
 
 IriSP.Widgets.Controller.prototype.showSearchBlock = function() {
-    this.$searchBlock.show("blind", { direction: "horizontal"}, 100);
+    this.$searchBlock.animate({ width:"160px" }, 200);
     this.$searchInput.css('background-color','#fff');
    
     this.$searchInput.focus();
@@ -233,25 +239,23 @@
     this._positiveMatch = false;
 
     // tell the world the field is open
-    this.player.popcorn.trigger("IriSP.search.open");
+    this.player.trigger("search.open");
 };
 
 IriSP.Widgets.Controller.prototype.hideSearchBlock = function() {
-    this._searchLastValue = this.$searchInput.val();
-    this.$searchInput.val('');
-    this.$searchBlock.hide("blind", { direction: "horizontal"}, 75);
-
+    this.$searchBlock.animate( { width: 0 }, 200);
     this._positiveMatch = false;
-    
-    this.player.popcorn.trigger("IriSP.search.closed");
+    this.player.trigger("search.closed");
 };
 
 /** react to clicks on the search button */
 IriSP.Widgets.Controller.prototype.searchButtonHandler = function() {
-    if ( this.$searchBlock.is(":hidden") ) {
+    if ( !this.$searchBlock.width() ) {
         this.showSearchBlock();
-        this.$searchInput.val(this._searchLastValue);      
-        this.player.popcorn.trigger("IriSP.search", this._searchLastValue); // trigger the search to make it more natural.
+        var _val = this.$searchInput.val();
+        if (_val) {
+            this.player.trigger("search", _val); // trigger the search to make it more natural.
+        }
 	} else {
         this.hideSearchBlock();
     }
@@ -260,16 +264,23 @@
 /** this handler is called whenever the content of the search
    field changes */
 IriSP.Widgets.Controller.prototype.searchHandler = function() {
-    this._searchLastValue = this.$searchInput.val();
+    if ( !this.$searchBlock.width() ) {
+        this.$searchBlock.css({ width:"160px" });
+        this.$searchInput.css('background-color','#fff');
+    }
+    var _val = this.$searchInput.val();
     this._positiveMatch = false;
-  
+    
     // do nothing if the search field is empty, instead of highlighting everything.
-    if (this._searchLastValue == "") {
-        this.player.popcorn.trigger("IriSP.search.cleared");
-        this.$searchInput.css('background-color','');
-    } else {
-        this.player.popcorn.trigger("IriSP.search", this._searchLastValue);
+    if (_val !== this.lastSearchValue) {
+        if (_val) {
+            this.player.trigger("search", _val);
+        } else {
+            this.player.trigger("search.cleared");
+            this.$searchInput.css('background-color','');
+        }
     }
+    this.lastSearchValue = _val;
 };
 
 /**
@@ -293,7 +304,7 @@
 IriSP.Widgets.Controller.prototype.triggeredSearch = function(searchString) {
     this.showSearchBlock();
     this.$searchInput.attr('value', searchString);      
-    this.player.popcorn.trigger("IriSP.search", searchString); // trigger the search to make it more natural.
+    this.player.trigger("search", searchString); // trigger the search to make it more natural.
 };
 
 
--- a/metadataplayer/metadataplayer/CreateAnnotation.css	Fri Aug 31 17:43:31 2012 +0200
+++ b/metadataplayer/metadataplayer/CreateAnnotation.css	Thu Sep 27 12:23:41 2012 +0200
@@ -11,9 +11,7 @@
 }
 
 .Ldt-CreateAnnotation-Inner {
-    background: url(img/pinstripe.png);
-    padding: 5px;
-    margin: 0;
+    background: url(img/pinstripe.png); padding: 5px; margin: 0; position: relative;
 }
 
 .Ldt-CreateAnnotation-Inner h3 {
@@ -86,11 +84,11 @@
     border: 1px solid #bbbbbb;
 }
 
-.Ldt-CreateAnnotation-Tags, .Ldt-CreateAnnotation-Polemics {
-    width: 520px;
+.Ldt-CreateAnnotation-RecBlock {
+    width: 220px; float: left;
 }
 
-.Ldt-CreateAnnotation-TagTitle, .Ldt-CreateAnnotation-PolemicTitle {
+.Ldt-CreateAnnotation-TagTitle, .Ldt-CreateAnnotation-PolemicTitle, .Ldt-CreateAnnotation-RecLabel {
     display: block; margin: 5px 0 2px; font-size: 12px;
 }
 
--- a/metadataplayer/metadataplayer/CreateAnnotation.js	Fri Aug 31 17:43:31 2012 +0200
+++ b/metadataplayer/metadataplayer/CreateAnnotation.js	Thu Sep 27 12:23:41 2012 +0200
@@ -1,4 +1,4 @@
-/* TODO: Add Social Network Sharing, Finish Current Timecode Sync & Arrow Takeover */
+/* TODO: Add Social Network Sharing */
 
 IriSP.Widgets.CreateAnnotation = function(player, config) {
     IriSP.Widgets.Widget.call(this, player, config);
@@ -11,8 +11,10 @@
     show_creator_field : true,
     start_visible : true,
     always_visible : false,
-    sync_on_slice_widget : true, /* If false, syncs on current timecode */
-    takeover_arrow : false,
+    show_slice : true,
+    show_arrow : true,
+    show_mic_record: false,
+    show_mic_play: false,
     minimize_annotation_widget : true,
     creator_name : "",
     creator_avatar : "",
@@ -37,6 +39,7 @@
         background_color: "#f0e000",
         text_color: "#000000"
     }],
+    slice_annotation_type: "chap",
     annotation_type: "Contributions",
     api_serializer: "ldt_annotate",
     api_endpoint_template: "",
@@ -91,16 +94,42 @@
 }
 
 IriSP.Widgets.CreateAnnotation.prototype.template =
-    '<div class="Ldt-CreateAnnotation"><div class="Ldt-CreateAnnotation-Inner">'
+    '{{#show_slice}}<div class="Ldt-CreateAnnotation-Slice"></div>{{/show_slice}}'
+    + '{{^show_slice}}{{#show_arrow}}<div class="Ldt-CreateAnnotation-Arrow"></div>{{/show_arrow}}{{/show_slice}}'
+    + '<div class="Ldt-CreateAnnotation"><div class="Ldt-CreateAnnotation-Inner">'
     + '<form class="Ldt-CreateAnnotation-Screen Ldt-CreateAnnotation-Main">'
     + '<h3><span class="Ldt-CreateAnnotation-h3Left">{{#show_title_field}}<input class="Ldt-CreateAnnotation-Title" placeholder="{{l10n.type_title}}" />{{/show_title_field}}'
     + '{{^show_title_field}}<span class="Ldt-CreateAnnotation-NoTitle">{{l10n.no_title}} </span>{{/show_title_field}}'
-    + ' <span class="Ldt-CreateAnnotation-Times">{{#sync_on_slice_widget}}{{l10n.from_time}} {{/sync_on_slice_widget}}{{^sync_on_slice_widget}}{{l10n.at_time}} {{/sync_on_slice_widget}} <span class="Ldt-CreateAnnotation-Begin">00:00</span>'
-    + '{{#sync_on_slice_widget}} {{l10n.to_time}} <span class="Ldt-CreateAnnotation-End">00:00</span>{{/sync_on_slice_widget}}</span></span>'
+    + ' <span class="Ldt-CreateAnnotation-Times">{{#show_slice}}{{l10n.from_time}} {{/show_slice}}{{^show_slice}}{{l10n.at_time}} {{/show_slice}} <span class="Ldt-CreateAnnotation-Begin">00:00</span>'
+    + '{{#show_slice}} {{l10n.to_time}} <span class="Ldt-CreateAnnotation-End">{{end}}</span>{{/show_slice}}</span></span>'
     + '{{#show_creator_field}}{{l10n.your_name_}} <input class="Ldt-CreateAnnotation-Creator" value="{{creator_name}}" /></h3>{{/show_creator_field}}'
     + '<textarea class="Ldt-CreateAnnotation-Description" placeholder="{{l10n.type_description}}"></textarea>'
     + '<div class="Ldt-CreateAnnotation-Avatar"><img src="{{creator_avatar}}" title="{{creator_name}}"></img></div>'
     + '<input type="submit" class="Ldt-CreateAnnotation-Submit" value="{{l10n.submit}}" />'
+    + '{{#show_mic_record}}<div class="Ldt-CreateAnnotation-RecBlock"><div class="Ldt-CreateAnnotation-RecLabel">Add voice annotation</div>'
+    + '    <object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" width="220" height="160">'
+    + '        <param name="movie" value="{{record_swf}}" />'
+    + '        <param name="quality" value="high" />'
+    + '        <param name="bgcolor" value="#ffffff" />'
+    + '        <param name="play" value="true" />'
+    + '        <param name="loop" value="true" />'
+    + '        <param name="wmode" value="transparent" />'
+    + '        <param name="scale" value="showall" />'
+    + '        <param name="menu" value="true" />'
+    + '        <param name="devicefont" value="false" />'
+    + '        <param name="salign" value="" />'
+    + '        <param name="allowScriptAccess" value="always" />'
+    + '        <param name="allowFullScreen" value="true" />'
+    + '        <param name="flashvars" value="playVisible={{show_mic_play}}">'
+    + '        <embed src="{{record_swf}}"" quality="high" bgcolor="#ffffff"'
+    + '             width="220" height="160" name="ExternalInterfaceExample" align="middle"'
+    + '             play="true" loop="false" quality="high" allowScriptAccess="always" '
+    + '             type="application/x-shockwave-flash" allowFullScreen="true" wmode="transparent" '
+    + '             flashvars="playVisible={{show_mic_play}}"'
+    + '             pluginspage="http://www.macromedia.com/go/getflashplayer">'
+    + '        </embed>'
+    + '    </object>'
+    + '</div>{{/show_mic_record}}' 
     + '{{#tags.length}}<div class="Ldt-CreateAnnotation-Tags"><div class="Ldt-CreateAnnotation-TagTitle">{{l10n.add_keywords_}}</div><ul class="Ldt-CreateAnnotation-TagList">'
     + '{{#tags}}<li class="Ldt-CreateAnnotation-TagLi" tag-id="{{id}}"><span class="Ldt-CreateAnnotation-TagButton">{{title}}</span></li>{{/tags}}</ul></div>{{/tags.length}}'
     + '{{#polemics.length}}<div class="Ldt-CreateAnnotation-Polemics"><div class="Ldt-CreateAnnotation-PolemicTitle">{{l10n.add_polemic_keywords_}}</div><ul class="Ldt-CreateAnnotation-PolemicList">'
@@ -113,6 +142,10 @@
     
 IriSP.Widgets.CreateAnnotation.prototype.draw = function() {
     var _this = this;
+    
+    this.begin = new IriSP.Model.Time();
+    this.end = this.source.getDuration();
+    
     if (this.tag_titles && !this.tags) {
         this.tags = IriSP._(this.tag_titles).map(function(_tag_title) {
             var _tag,
@@ -137,7 +170,44 @@
             });
         /* We have to use the map function because Mustache doesn't like our tags object */
     }
+    this.record_swf = IriSP.getLib("recordMicSwf");
     this.renderTemplate();
+    if (this.show_mic_record) {
+        this.recorder = this.$.find("embed")[0];
+        
+        window.setAudioUrl = function(_url) {
+            _this.audio_url = _url;
+        }
+    }
+    if (this.show_slice) {
+        this.insertSubwidget(
+            this.$.find(".Ldt-CreateAnnotation-Slice"),
+            {
+                type: "Slice",
+                show_arrow: this.show_arrow,
+                annotation_type: this.slice_annotation_type,
+                onBoundsChanged: function(_from, _to) {
+                    _this.begin = new IriSP.Model.Time(_from || 0);
+                    _this.end = new IriSP.Model.Time(_to || 0);
+                    _this.$.find(".Ldt-CreateAnnotation-Begin").html(_this.begin.toString());
+                    _this.$.find(".Ldt-CreateAnnotation-End").html(_this.end.toString());
+                }
+            },
+            "slice"
+        );
+    } else {
+        if (this.show_arrow) {
+            this.insertSubwidget(this.$.find(".Ldt-CreateAnnotation-Arrow"), {type: "Arrow"},"arrow");
+        }
+        this.onMediaEvent("timeupdate", function(_time) {
+            _this.begin = new IriSP.Model.Time(_time || 0);
+            _this.end = new IriSP.Model.Time(_time || 0);
+            _this.$.find(".Ldt-CreateAnnotation-Begin").html(_this.begin.toString());
+            if (_this.arrow) {
+                _this.arrow.moveToTime(_time);
+            }
+        });
+    }
     this.$.find(".Ldt-CreateAnnotation-Close").click(function() {
         _this.close_after_send
         ? _this.hide()
@@ -163,10 +233,7 @@
         this.hide();
     }
     
-    this.bindPopcorn("IriSP.CreateAnnotation.toggle","toggle");
-    this.bindPopcorn("IriSP.Slice.boundsChanged","onBoundsChanged");
-    this.begin = new IriSP.Model.Time();
-    this.end = this.source.getDuration();
+    this.onMdpEvent("CreateAnnotation.toggle","toggle");
     this.$.find("form").submit(this.functionWrapper("onSubmit"));
 }
 
@@ -188,19 +255,20 @@
     this.$.find(".Ldt-CreateAnnotation-TagLi, .Ldt-CreateAnnotation-PolemicLi").removeClass("selected");
     this.$.slideDown();
     if (this.minimize_annotation_widget) {
-        this.player.popcorn.trigger("IriSP.Annotation.minimize");
+        this.player.trigger("Annotation.minimize");
     }
-    this.player.popcorn.trigger("IriSP.Slice.show");
 }
 
 IriSP.Widgets.CreateAnnotation.prototype.hide = function() {
+    if (this.recorder) {
+        this.recorder.stopRecord();
+    }
     if (!this.always_visible) {
         this.visible = false;
         this.$.slideUp();
         if (this.minimize_annotation_widget) {
-            this.player.popcorn.trigger("IriSP.Annotation.maximize");
+            this.player.trigger("Annotation.maximize");
         }
-        this.player.popcorn.trigger("IriSP.Slice.hide");
     }
 }
 
@@ -214,13 +282,6 @@
     }
 }
 
-IriSP.Widgets.CreateAnnotation.prototype.onBoundsChanged = function(_values) {
-    this.begin = new IriSP.Model.Time(_values[0] || 0);
-    this.end = new IriSP.Model.Time(_values[1] || 0);
-    this.$.find(".Ldt-CreateAnnotation-Begin").html(this.begin.toString());
-    this.$.find(".Ldt-CreateAnnotation-End").html(this.end.toString());
-}
-
 IriSP.Widgets.CreateAnnotation.prototype.addKeyword = function(_keyword) {
     var _field = this.$.find(".Ldt-CreateAnnotation-Description"),
         _rx = IriSP.Model.regexpFromTextOrArray(_keyword),
@@ -234,8 +295,8 @@
 }
 
 IriSP.Widgets.CreateAnnotation.prototype.pauseOnWrite = function() {
-    if (this.pause_on_write && !this.player.popcorn.media.paused) {
-        this.player.popcorn.pause();
+    if (this.pause_on_write && !this.media.getPaused()) {
+        this.media.pause();
     }
 }
 
@@ -279,6 +340,10 @@
         return;
     }
     
+    if (this.recorder) {
+        this.recorder.stopRecord();
+    }
+    
     var _exportedAnnotations = new IriSP.Model.List(this.player.sourceManager), /* Création d'une liste d'annotations contenant une annotation afin de l'envoyer au serveur */
         _export = this.player.sourceManager.newLocalSource({serializer: IriSP.serializers[this.api_serializer]}), /* Création d'un objet source utilisant un sérialiseur spécifique pour l'export */
         _annotation = new IriSP.Model.Annotation(false, _export), /* Création d'une annotation dans cette source avec un ID généré à la volée (param. false) */
@@ -312,6 +377,14 @@
     _annotation.setTags(this.$.find(".Ldt-CreateAnnotation-TagLi.selected")
         .map(function() { return IriSP.jQuery(this).attr("tag-id")})); /*Liste des ids de tags */
     
+    if (this.audio_url) {
+        _annotation.audio = {
+            src: "mic",
+            mimetype: "audio/mp3",
+            href: this.audio_url
+        };
+    }
+    
     /* Les données créateur/date de création sont envoyées non pas dans l'annotation, mais dans le projet */
     if (this.show_creator_field) {
         _export.creator = this.$.find(".Ldt-CreateAnnotation-Creator").val();
@@ -344,10 +417,10 @@
             _export.getAnnotations().removeElement(_annotation, true); /* Pour éviter les doublons, on supprime l'annotation qui a été envoyée */
             _export.deSerialize(_data); /* On désérialise les données reçues pour les réinjecter */
             _this.source.merge(_export); /* On récupère les données réimportées dans l'espace global des données */
-            if (_this.pause_on_write && _this.player.popcorn.media.paused) {
-                _this.player.popcorn.play();
+            if (_this.pause_on_write && _this.media.getPaused()) {
+                _this.media.play();
             }
-            _this.player.popcorn.trigger("IriSP.AnnotationsList.refresh"); /* On force le rafraîchissement du widget AnnotationsList */
+            _this.player.trigger("AnnotationsList.refresh"); /* On force le rafraîchissement du widget AnnotationsList */
         },
         error: function(_xhr, _error, _thrown) {
             IriSP.log("Error when sending annotation", _thrown);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/metadataplayer/metadataplayer/DailymotionPlayer.js	Thu Sep 27 12:23:41 2012 +0200
@@ -0,0 +1,104 @@
+IriSP.Widgets.DailymotionPlayer = function(player, config) {
+    IriSP.Widgets.Widget.call(this, player, config);
+};
+
+IriSP.Widgets.DailymotionPlayer.prototype = new IriSP.Widgets.Widget();
+
+IriSP.Widgets.DailymotionPlayer.prototype.defaults = {
+    aspect_ratio: 14/9
+}
+
+IriSP.Widgets.DailymotionPlayer.prototype.draw = function() {
+    
+    if (typeof this.video === "undefined") {
+        this.video = this.media.video;
+    }
+
+    this.height = this.height || Math.floor(this.width / this.aspect_ratio);
+    
+    var _media = this.media,
+        _this = this,
+        _pauseState = true;
+    
+    /* Dailymotion utilise un système de fonctions référencées dans
+     * des variables globales pour la gestion des événements.
+     */
+    
+    window.onDailymotionPlayerReady = function() {
+
+        var _player = document.getElementById(_this.container);
+        
+        _media.getCurrentTime = function() {
+            return new IriSP.Model.Time(1000*_player.getCurrentTime());
+        }
+        _media.getVolume = function() {
+            return _player.getVolume() / 100;
+        }
+        _media.getPaused = function() {
+            return _pauseState;
+        }
+        _media.getMuted = function() {
+            return _player.isMuted();
+        }
+        _media.setCurrentTime = function(_milliseconds) {
+            _seekPause = _pauseState;
+            return _player.seekTo(_milliseconds / 1000);
+        }
+        _media.setVolume = function(_vol) {
+            return _player.setVolume(Math.floor(_vol*100));
+        }
+        _media.mute = function() {
+            return _player.mute();
+        }
+        _media.unmute = function() {
+            return _player.unMute();
+        }
+        _media.play = function() {
+            return _player.playVideo();
+        }
+        _media.pause = function() {
+            return _player.pauseVideo();
+        }
+        
+        _player.addEventListener("onStateChange", "onDailymotionStateChange");
+        _player.addEventListener("onVideoProgress", "onDailymotionVideoProgress");
+        
+        _player.cueVideoByUrl(_this.video);
+        
+        _media.trigger("loadedmetadata");
+    }
+    
+    window.onDailymotionStateChange = function(_state) {
+        switch(_state) {
+            case 1:
+                _media.trigger("play");
+                _pauseState = false;
+                break;
+    
+            case 2:
+                _media.trigger("pause");
+                _pauseState = true;
+                break;
+    
+            case 3:
+                _media.trigger("seeked");
+                break;
+        }
+    }
+    
+    window.onDailymotionVideoProgress = function(_progress) {
+        _media.trigger("timeupdate", new IriSP.Model.Time(_progress.mediaTime * 1000));
+    }
+    
+    var params = {
+        "allowScriptAccess" : "always",
+        "wmode": "opaque"
+    };
+    
+    var atts = {
+        id : this.container
+    };
+
+    swfobject.embedSWF("http://www.dailymotion.com/swf?chromeless=1&enableApi=1", this.container, this.width, this.height, "8", null, null, params, atts);
+    
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/metadataplayer/metadataplayer/JwpPlayer.js	Thu Sep 27 12:23:41 2012 +0200
@@ -0,0 +1,119 @@
+IriSP.Widgets.JwpPlayer = function(player, config) {
+    IriSP.Widgets.Widget.call(this, player, config);
+};
+
+IriSP.Widgets.JwpPlayer.prototype = new IriSP.Widgets.Widget();
+
+IriSP.Widgets.JwpPlayer.prototype.defaults = {
+}
+
+IriSP.Widgets.JwpPlayer.prototype.draw = function() {
+    
+    var _opts = {},
+        _player = jwplayer(this.$[0]),
+        _seekPause = false,
+        _pauseState = true,
+        _props = [ "live", "provider", "autostart" ];
+    
+    if (typeof this.video === "undefined") {
+        this.video = this.media.video;
+    }
+    
+    if (typeof this.streamer === "function") {
+        this.streamer = this.streamer(this.video);
+    }
+
+    if (typeof this.streamer === "string" && (this.provider === "http" || this.provider === "rtmp")) {
+        this.video = this.video.replace(this.streamer,"");
+        _opts.streamer = this.streamer;
+    }
+        
+    _opts.file = this.video;
+    _opts.flashplayer = IriSP.getLib("jwPlayerSWF");
+    _opts["controlbar.position"] = "none";
+    _opts.width = this.width;
+    _opts.height = this.height || Math.floor(.643*this.width);
+    
+    for (var i = 0; i < _props.length; i++) {
+        if (typeof this[_props[i]] !== "undefined") {
+            _opts[_props[i]] = this[_props[i]];
+        }
+    }
+    
+    if (this.autostart) {
+        _pauseState = false;
+        this.media.trigger("play");
+    }
+    // Binding functions to jwplayer
+
+    this.media.getCurrentTime = function() {
+        return new IriSP.Model.Time(1000*_player.getPosition() || 0);
+    }
+    this.media.getVolume = function() {
+        return _player.getVolume() / 100;
+    }
+    this.media.getPaused = function() {
+        return _pauseState;
+    }
+    this.media.getMuted = function() {
+        return _player.getMute();
+    }
+    this.media.setCurrentTime = function(_milliseconds) {
+        _seekPause = _pauseState;
+        return _player.seek(_milliseconds / 1000);
+    }
+    this.media.setVolume = function(_vol) {
+        return _player.setVolume(Math.floor(_vol*100));
+    }
+    this.media.mute = function() {
+        return _player.setMute(true);
+    }
+    this.media.unmute = function() {
+        return _player.setMute(false);
+    }
+    this.media.play = function() {
+        return _player.play(true);
+    }
+    this.media.pause = function() {
+        return _player.pause(true);
+    }
+    
+    // Binding jwplater events to media
+    
+    var _media = this.media;
+    
+    _opts.events = {
+        onReady: function() {
+            _media.trigger("loadedmetadata");
+        },
+        onTime: function(_progress) {
+            if (_seekPause) {
+                _player.pause(true);
+                _seekPause = false;
+            } else {
+                if (_pauseState && _player.getState() === "PLAYING") {
+                    _pauseState = false;
+                    _media.trigger("play");
+                }
+            }
+            _media.trigger("timeupdate", new IriSP.Model.Time(_progress.position * 1000));
+        },
+        onPlay: function() {
+            if (!_seekPause) {
+                _pauseState = false;
+                _media.trigger("play");
+            }
+        },
+        onPause: function() {
+            _pauseState = true;
+            _media.trigger("pause");
+        },
+        onSeek: function() {
+            _media.trigger("seeked");
+        }
+    }
+    _player.setup(_opts);
+    
+    this.jwplayer = _player;
+    
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/metadataplayer/metadataplayer/KnowledgeConcierge.css	Thu Sep 27 12:23:41 2012 +0200
@@ -0,0 +1,7 @@
+.Ldt-Kc-Slider {
+    width: 100%; height: 8px; margin: 3px 0; font-size: 8px;
+}
+
+.Ldt-Kc-Canvas {
+    border: 1px solid #999999;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/metadataplayer/metadataplayer/KnowledgeConcierge.js	Thu Sep 27 12:23:41 2012 +0200
@@ -0,0 +1,260 @@
+IriSP.Widgets.KnowledgeConcierge = function(player, config) {
+    IriSP.Widgets.Widget.call(this, player, config);
+};
+
+IriSP.Widgets.KnowledgeConcierge.prototype = new IriSP.Widgets.Widget();
+
+IriSP.Widgets.KnowledgeConcierge.prototype.defaults = {
+    width: 600,
+    height: 500,
+    sketch_path: "tmgraph",
+    sketch_files: [ "tmgraph.pde", "physics.pde", "model.pde", "javascript.pde", "menu.pde", "event.pde", "constants.pde", "initialdata.pde"],
+    api_root: "/kn-concierge/",
+    use_word_boundaries: false
+}
+
+IriSP.Widgets.KnowledgeConcierge.prototype.messages = {
+    "fr": {
+    },
+    "en": {
+    }
+}
+
+IriSP.Widgets.KnowledgeConcierge.prototype.template =
+    '<div class="Ldt-Kc-Slider"></div><canvas class="Ldt-Kc-Canvas" />';
+
+IriSP.Widgets.KnowledgeConcierge.prototype.draw = function() {
+    this.renderTemplate();
+    var _canvasHeight = this.height - 16,
+        _canvasWidth = this.width - 2,
+        _canvas = this.$.find(".Ldt-Kc-Canvas"),
+        _tmpId = IriSP._.uniqueId("Processing-"),
+        _slider = this.$.find(".Ldt-Kc-Slider");
+    _canvas.attr({
+        width: _canvasWidth,
+        height: _canvasHeight,
+        id: _tmpId
+    }).css({
+        width: _canvasWidth,
+        height: _canvasHeight
+    });
+    var _this = this,
+        _pjsfiles = IriSP._(this.sketch_files).map(function(_f) { return _this.sketch_path + "/" + _f }),
+        _selectedText = "";
+    Processing.loadSketchFromSources(_canvas[0],_pjsfiles);
+    
+    function triggerSearch(text) {
+        if (_selectedText !== text) {
+            //console.log("Trigger search for '" + text + "'");
+            _selectedText = text;
+            _this.player.trigger("search.triggeredSearch", text);
+        }
+    }
+    
+    function searchNodes(tags) {
+        var _tlist = (_this.use_word_boundaries ? IriSP._(tags).map(function(t) { return "\\\\y" + t + "\\\\y" }) : tags),
+            _q = "(?i)(" + _tlist.join("|") + ")";
+        jQuery.getJSON(
+            _this.api_root + "topics.jsp",
+            {
+                proj: _this.project_id,
+                q: _q
+            },
+            function(data) {
+                if (data && data.items && data.items.length) {
+                    for (var i=0, l=data.items.length; i<l; i++) {
+                        var node = data.items[i];
+                        if (i == 0) {
+                            _pjs.initNode(node.id, node.name, node.grp, node.uid, node.proj);
+                            var node = _pjs.findNode(node.id, node.proj);
+                        } else {
+                            var node = _pjs.newNode(node.id, node.name, node.grp, node.uid, node.proj);
+                            node.root = true;
+                            node.fix();
+                        }
+                        _fns.countassoc(node.id, node.proj);
+                        if (l > 1) {
+                            node.position(Math.floor(200*Math.sin(2 * Math.PI * i / l)),Math.floor(200*Math.cos(2 * Math.PI * i / l)));
+                        }
+                    }
+                } else {
+                    console.log("No match found");
+                }
+            }
+        );
+    }
+    
+    function rootNode(id, proj) {
+        jQuery.getJSON(
+            _this.api_root + "topic.jsp",
+            {
+                id: id,
+                proj: proj
+            },
+            function(response) {
+                if (response != null && response.items.length > 0){
+                    item = response.items[0];
+                    _pjs.initNode(item.id, item.name, item.grp, item.uid, item.proj);
+                    _fns.countassoc(item.id, item.proj);
+                } else {
+                    console.debug('No such topic.');
+                }
+        });
+    }
+    
+    function bindJavascript() {
+        _pjs = Processing.getInstanceById(_tmpId);
+        if (_pjs && typeof _pjs.bindJavascript === "function") {
+            setTimeout(function() {
+                _pjs.bindJavascript(_fns);
+                _pjs.setSize(_canvasWidth,_canvasHeight);
+                var _edit = false,
+                    _teamMode = true;
+                _pjs.saveMode("en",false,_teamMode,false,"both",_edit);
+                rootNode(_this.topic_id, _this.project_id);
+                _canvas.click(function() {
+                    triggerSearch("")
+                });
+                _slider.slider({
+                    min: -20,
+                    max: 20,
+                    value: 0,
+                    range: "min",
+                    slide: function(event, ui) {
+                        _pjs.zoom(Math.exp(ui.value / 10));
+                    }
+                });
+            }, 1000);
+        } else {
+            setTimeout(bindJavascript, 1000); 
+        }
+    }
+    
+    var _fns = {
+        adjacentnodes: function(id, proj, adj, both) {
+            //console.log("Function adjacentnodes called with", arguments);
+            jQuery.ajax({
+                url: _this.api_root + "associations-bd.jsp",
+                cache: false,
+                data: {
+                    id: id,
+                    proj: proj,
+                    both: both,
+                    adj: adj
+                },
+                success: function(response) {
+                    if (response.items.length > 0){
+                        for(i = 0, end = response.items.length; i < end; i++) {
+                            item = response.items[i];
+                            _pjs.addEdge(item.asc_id, item.id, item.from_proj, item.to_id, item.to_proj,
+                                        item.r_name, item.r_from,  item.r_to, item.uid, item.proj);
+                            _pjs.setNodeName( item.id,   item.from_proj,item.name);
+                            _pjs.setNodeValue(item.id,   item.from_proj,item.name,   item.grp,   item.abst,   item.from_uid);
+                            if (item.from_assoc!=null) {
+                                _pjs.setNodeAssoc(item.id, item.from_proj, item.from_assoc);
+                            }
+                            _pjs.setNodeName( item.to_id,item.to_proj,  item.to_name);
+                            _pjs.setNodeValue(item.to_id,item.to_proj,  item.to_name,item.to_grp,item.to_abst,item.to_uid);
+                            if (item.to_assoc!=null) {
+                                _pjs.setNodeAssoc(item.to_id, item.to_proj, item.to_assoc);
+                            }
+                        }
+                        return response;
+                    } else {
+                        console.debug('No such topic.');
+                        return null;
+                    }
+                }
+            });
+        },
+        selectnode: function(id, proj) {
+            //console.log("Function selectnode called with", arguments);
+            /* Originally, open dialog with info from
+             * /kn-concierge/topic.jsp?id={{id}}&proj={{proj}}
+             * /kn-concierge/topicContent.jsp?id={{id}}&proj={{proj}}
+             */
+        },
+        selectedge: function(asc_id) {
+            //console.log("Function selectedge called with", arguments);
+            /* /kn-concierge/association.jsp?asc_id={{asc_id}}&proj={{proj}}" */
+        },
+        topicnode: function(id){
+            //console.log("Function topicnode called with", arguments);
+        },
+        setscale: function(scl){
+            //console.log("Function setscale called with", arguments);
+            _slider.slider("value", 10*Math.log(scl));
+        },
+        group_shapes: function(){
+            //console.log("Function group_shapes");
+        },
+        allbackup: function(){
+        },
+        allretrieve: function(){
+        },
+        new_topic: function(){
+        },
+        pedia: function() {
+        },
+        set_mode: function(){
+        },
+        countassoc: function(id, proj) {
+            //console.log("Fonction countassoc called with", arguments);
+            jQuery.ajax({
+                url: _this.api_root + "count-assoc.jsp",
+                data: {
+                    id: id,
+                    proj: proj
+                },
+                success: function(response) {
+                    if (response.items.length > 0){
+                        for(i = 0, end = response.items.length; i < end; i++) {
+                            item = response.items[i];
+                            _pjs.setNodeValue(item.id, item.proj, item.name, item.grp, item.abst);
+                            if (item.assoc!=null) _pjs.setNodeAssoc(item.id, item.proj, item.assoc);
+                            if (item.mass!=null) _pjs.setNodeMass( item.id, item.proj, item.mass);
+                        }
+                    } else {
+                        console.debug('No such topic.');
+                    }
+                }
+            });
+        },
+        new_relation: function() {
+        },
+        new_select: function(id, proj) {
+            var node = _pjs.findNode(id, proj);
+            triggerSearch(node.name);
+            //console.log("Mouse over node named '" + node.name + "'");
+        },
+        startexpand: function() {
+            //console.log("Function startexpand()");
+        },
+        endexpand: function() {
+            //console.log("Function endexpand()");
+        },
+        username: function() {
+            //console.log("Function username()");
+        }
+    }
+    
+    this.getWidgetAnnotations().forEach(function(annotation) {
+        annotation.on("click", function() {
+            var _tags = annotation.getTagTexts();
+            if (_tags.length) {
+                searchNodes(_tags);
+            }
+        });
+    });
+    
+    this.source.getTags().forEach(function(tag) {
+        tag.on("click", function() {
+            if (tag.title) {
+                searchNodes([tag.title]);
+            }
+        });
+    });
+    
+    bindJavascript();
+    
+}
--- a/metadataplayer/metadataplayer/LdtPlayer-core.js	Fri Aug 31 17:43:31 2012 +0200
+++ b/metadataplayer/metadataplayer/LdtPlayer-core.js	Thu Sep 27 12:23:41 2012 +0200
@@ -20,11 +20,11 @@
     v2.0.3 (c) Kyle Simpson
     MIT License
 */
-(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}if(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);/* init.js - initialization and configuration of Popcorn and the widgets
+(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}if(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);/* init.js - initialization and configuration of the widgets
 */
 
 if (typeof window.IriSP === "undefined") {
-    IriSP = {};
+    window.IriSP = {};
 }
 
 /* The Metadataplayer Object, single point of entry, replaces IriSP.init_player */
@@ -32,43 +32,34 @@
 IriSP.Metadataplayer = function(config) {
     IriSP.log("IriSP.Metadataplayer constructor");
     for (var key in IriSP.guiDefaults) {
-        if (IriSP.guiDefaults.hasOwnProperty(key) && !config.gui.hasOwnProperty(key)) {
-            config.gui[key] = IriSP.guiDefaults[key]
+        if (IriSP.guiDefaults.hasOwnProperty(key) && !config.hasOwnProperty(key)) {
+            config[key] = IriSP.guiDefaults[key]
         }
     }
-    var _container = document.getElementById(config.gui.container);
+    var _container = document.getElementById(config.container);
     _container.innerHTML = '<h3 class="Ldt-Loader">Loading... Chargement...</h3>';
     this.sourceManager = new IriSP.Model.Directory();
     this.config = config;
-    this.callbackQueue = [];
-    this.isLoaded = false;
+    this.__events = {};
     this.loadLibs();
 }
 
 IriSP.Metadataplayer.prototype.toString = function() {
-    return 'Metadataplayer in #' + this.config.gui.container;
-}
-
-IriSP.Metadataplayer.prototype.deferCallback = function(_callback) {
-    var _this = this;
-    IriSP._.defer(function() {
-        _callback.call(_this);
-    });
+    return 'Metadataplayer in #' + this.config.container;
 }
 
-IriSP.Metadataplayer.prototype.handleCallbacks = function() {
-    this.isLoaded = true;
-    while (this.callbackQueue.length) {
-        this.deferCallback(this.callbackQueue.splice(0,1)[0]);
+IriSP.Metadataplayer.prototype.on = function(_event, _callback) {
+    if (typeof this.__events[_event] === "undefined") {
+        this.__events[_event] = [];
     }
+    this.__events[_event].push(_callback);
 }
 
-IriSP.Metadataplayer.prototype.onLoad = function(_callback) {
-    if (this.isLoaded) {
-        this.deferCallback(_callback);
-    } else {
-        this.callbackQueue.push(_callback);
-    }
+IriSP.Metadataplayer.prototype.trigger = function(_event, _data) {
+    var _element = this;
+    IriSP._(this.__events[_event]).each(function(_callback) {
+        _callback.call(_element, _data);
+    });
 }
 
 IriSP.Metadataplayer.prototype.loadLibs = function() {
@@ -76,8 +67,7 @@
     var $L = $LAB
         .script(IriSP.getLib("underscore"))
         .script(IriSP.getLib("Mustache"))
-        .script(IriSP.getLib("jQuery"))
-        .script(IriSP.getLib("swfObject"));
+        .script(IriSP.getLib("jQuery"));
     
     if (typeof JSON == "undefined") {
         $L.script(IriSP.getLib("json"));
@@ -86,17 +76,9 @@
     $L.wait()
         .script(IriSP.getLib("jQueryUI"));
 
-    if (this.config.player.type === "jwplayer" || this.config.player.type === "auto") {
-        $L.script(IriSP.getLib("jwplayer"));
-    }
-    
-    if (this.config.player.type !== "jwplayer" && this.config.player.type !== "allocine" && this.config.player.type !== "dailymotion") {
-        $L.script(IriSP.getLib("popcorn"));
-    }
-
     /* widget specific requirements */
-    for(var _i = 0; _i < this.config.gui.widgets.length; _i++) {
-        var _t = this.config.gui.widgets[_i].type;
+    for(var _i = 0; _i < this.config.widgets.length; _i++) {
+        var _t = this.config.widgets[_i].type;
         if (typeof IriSP.widgetsRequirements[_t] !== "undefined" && typeof IriSP.widgetsRequirements[_t].requires !== "undefined" ) {
             for (var _j = 0; _j < IriSP.widgetsRequirements[_t].requires.length; _j++) {
                 $L.script(IriSP.getLib(IriSP.widgetsRequirements[_t].requires[_j]));
@@ -117,25 +99,28 @@
         IriSP.jQuery = window.jQuery.noConflict();
     }
     if (typeof IriSP._ === "undefined" && typeof window._ !== "undefined") {
-        IriSP._ = window._.noConflict();
+        IriSP._ = window._;
     }
     IriSP.loadCss(IriSP.getLib("cssjQueryUI"));
-    IriSP.loadCss(this.config.gui.css);
+    IriSP.loadCss(this.config.css);
     
-    this.videoData = this.loadMetadata(this.config.player.metadata);
-    this.$ = IriSP.jQuery('#' + this.config.gui.container);
+    this.$ = IriSP.jQuery('#' + this.config.container);
     this.$.css({
-        "width": this.config.gui.width,
+        "width": this.config.width,
         "clear": "both"
     });
-    if (typeof this.config.gui.height !== "undefined") {
-        this.$.css("height", this.config.gui.height);
+    if (typeof this.config.height !== "undefined") {
+        this.$.css("height", this.config.height);
     }
       
+    this.widgets = [];
     var _this = this;
-    this.videoData.onLoad(function() {
-        _this.onVideoDataLoaded();
-    });
+    for(var i = 0; i < this.config.widgets.length; i++) {
+        this.loadWidget(this.config.widgets[i], function(_widget) {
+            _this.widgets.push(_widget)
+        });
+    };
+    this.$.find('.Ldt-Loader').detach();
 }
 
 IriSP.Metadataplayer.prototype.loadMetadata = function(_metadataInfo) {
@@ -152,187 +137,6 @@
     }
 }
 
-IriSP.Metadataplayer.prototype.onVideoDataLoaded = function() {
-    
-    /* Setting default media from metadata */
-   
-    if (typeof this.videoData !== "undefined") {
-        
-        var _media;
-        
-        if (typeof this.videoData.mainMedia !== "undefined") {
-            _media = this.videoData.getElement(this.videoData.mainMedia);
-        }
-        
-        if (this.config.player.type === "mashup" || this.config.player.type === "mashup-html") {
-            if (typeof _media === "undefined" || _media.elementType !== "mashup") {
-                var _mashups = this.videoData.getMashups();
-                if (_mashups.length) {
-                    _media = _mashups[0];
-                }
-            }
-        } else {
-            if (typeof _media === "undefined" || _media.elementType !== "media") {
-                var _medias = this.videoData.getMedias();
-                if (_medias.length) {
-                    _media = _medias[0];
-                }
-            }
-        }
-        
-        this.videoData.currentMedia = _media;
-        
-        /* Getting video URL from metadata if it's not in the player config options */
-        
-        if (typeof _media !== "undefined" && typeof _media.video !== "undefined" && typeof this.config.player.video === "undefined") {
-            this.config.player.video = _media.video;
-            if (typeof this.config.player.streamer == "undefined" && typeof _media.streamer !== "undefined") {
-                this.config.player.streamer = _media.streamer;
-            }
-        }
-        
-    }
-    
-    if (typeof this.config.player.video === "string" && this.config.player.url_transform === "function") {
-        this.config.player.video = this.config.player.url_transform(this.config.player.video);
-    }
-    
-    var _pop,
-        _divs = this.layoutDivs("video",this.config.player.height || undefined),
-        containerDiv = _divs[0],
-        spacerDiv = _divs[1],
-        _this = this,
-        _types = {
-            "html5" : /\.(ogg|ogv|webm)$/,
-            "youtube" : /^(https?:\/\/)?(www\.)?youtube\.com/,
-            "dailymotion" : /^(https?:\/\/)?(www\.)?dailymotion\.com/
-        };
-    
-    if (this.config.player.type === "auto") {
-        this.config.player.type = "jwplayer";
-        IriSP._(_types).each(function(_v, _k) {
-            if (_v.test(_this.config.player.video)) {
-                _this.config.player.type = _k
-            }
-        });
-    }
-
-    switch(this.config.player.type) {
-        case "html5":
-            var _tmpId = Popcorn.guid("video"),
-                _videoEl = IriSP.jQuery('<video>');
-            
-            _videoEl.attr({
-                "src" : this.config.player.video,
-                "id" : _tmpId
-            })
-
-            if(this.config.player.hasOwnProperty("width")) {
-                _videoEl.attr("width", this.config.player.width);
-            }
-            if(this.config.player.hasOwnProperty("height")) {
-                _videoEl.attr("height", this.config.player.height);
-            }
-            IriSP.jQuery("#" + containerDiv).append(_videoEl);
-            _pop = Popcorn("#" + _tmpId);
-            break;
-
-        case "html5-audio":
-            var _tmpId = Popcorn.guid("audio"),
-                _videoEl = IriSP.jQuery('<audio>');
-            
-            _videoEl.attr({
-                "src" : this.config.player.video,
-                "id" : _tmpId
-            })
-
-            if(this.config.player.hasOwnProperty("width")) {
-                _videoEl.attr("width", this.config.player.width);
-            }
-            if(this.config.player.hasOwnProperty("height")) {
-                _videoEl.attr("height", this.config.player.height);
-            }
-            IriSP.jQuery("#" + containerDiv).append(_videoEl);
-            _pop = Popcorn("#" + _tmpId);
-            break;
-
-        case "jwplayer":
-            var opts = IriSP.jQuery.extend({}, this.config.player);
-            delete opts.container;
-            delete opts.type;
-            if (typeof opts.streamer === "function") {
-                opts.streamer = opts.streamer(opts.video);
-            }
-            if (typeof opts.streamer === "string") {
-                opts.video = opts.video.replace(opts.streamer,"");
-            }
-            opts.file = opts.video;
-            delete opts.video;
-            delete opts.metadata;
-
-            if(!opts.hasOwnProperty("flashplayer")) {
-                opts.flashplayer = IriSP.getLib("jwPlayerSWF");
-            }
-
-            if(!opts.hasOwnProperty("controlbar.position")) {
-                opts["controlbar.position"] = "none";
-            }
-            _pop = new IriSP.PopcornReplacement.jwplayer("#" + containerDiv, opts);
-            break;
-
-        case "youtube":
-            // Popcorn.youtube wants us to specify the size of the player in the style attribute of its container div.
-            IriSP.jQuery("#" + containerDiv).css({
-                width : this.config.player.width + "px",
-                height : this.config.player.height + "px"
-            });
-            var _urlparts = this.config.player.video.split(/[?&]/),
-                _params = {};
-            for (var _j = 1; _j < _urlparts.length; _j++) {
-                var _ppart = _urlparts[_j].split('=');
-                _params[_ppart[0]] = decodeURIComponent(_ppart[1]);
-            }
-            _params.controls = 0;
-            _params.modestbranding = 1;
-            _url = _urlparts[0] + '?' + IriSP.jQuery.param(_params);
-            _pop = Popcorn.youtube("#" + containerDiv, _url);
-            break;
-
-        case "dailymotion":
-            _pop = new IriSP.PopcornReplacement.dailymotion("#" + containerDiv, this.config.player);
-            break;
-
-        case "mashup":
-            _pop = new IriSP.PopcornReplacement.mashup("#" + containerDiv, this.config.player);
-            break;
-            
-        case "allocine":
-            _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;
-    };
-
-    this.popcorn = _pop;
-    
-    /* Now Loading Widgets */
-    
-    this.widgets = [];
-    var _this = this;
-    for(var i = 0; i < this.config.gui.widgets.length; i++) {
-        this.loadWidget(this.config.gui.widgets[i], function(_widget) {
-            _this.widgets.push(_widget)
-        });
-    };
-    this.$.find('.Ldt-Loader').detach();
-    this.handleCallbacks();
-}
-
 IriSP.Metadataplayer.prototype.loadWidget = function(_widgetConfig, _callback) {
     /* Creating containers if needed */
     if (typeof _widgetConfig.container === "undefined") {
@@ -366,20 +170,20 @@
     if (typeof(_name) === "undefined") {
        _name = "";
     }
-    var newDiv = IriSP._.uniqueId(this.config.gui.container + "_widget_" + _name + "_"),
+    var newDiv = IriSP._.uniqueId(this.config.container + "_widget_" + _name + "_"),
         spacerDiv = IriSP._.uniqueId("LdtPlayer_spacer_"),
         divHtml = IriSP.jQuery('<div>')
             .attr("id",newDiv)
             .css({
-                width: this.config.gui.width + "px",
+                width: this.config.width + "px",
                 position: "relative",
                 clear: "both"
             }),
         spacerHtml = IriSP.jQuery('<div>')
             .attr("id",spacerDiv)
             .css({
-                width: this.config.gui.width + "px",
-                height: this.config.gui.spacer_div_height + "px",
+                width: this.config.width + "px",
+                height: this.config.spacer_div_height + "px",
                 position: "relative",
                 clear: "both"
             });
@@ -428,154 +232,7 @@
         console.log.apply(console, arguments);
     }
 }
-/* wrapper that simulates popcorn.js because
-   popcorn is a bit unstable at the time */
-
-/* Popcorn.code replacement has been disabled. It didn't work properly and was not even used  */
-
-IriSP.PopcornReplacement = {  
-};
-
-/** base class for our popcorn-compatible players.
- */
-IriSP.PopcornReplacement.player = function(container, options) {
-  
-    this.media = { 
-        "paused": true,
-        "muted": false
-    };
-    
-    this.container = container.replace(/^#/,''); //remove '#' at beginning
-    this.msgPump = {}; /* dictionnary used to receive and send messages */
-    this._options = options;
-
-};
-
-IriSP.PopcornReplacement.player.prototype.listen = function(msg, callback) {
-    if (!this.msgPump.hasOwnProperty(msg)) {
-        this.msgPump[msg] = [];
-    }
-    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;
-    }
-    var d = this.msgPump[msg];
-    for(var i = 0; i < d.length; i++) {
-        d[i].call(window, params);
-    }
-};
-
-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);
-      return v.toString(16);
-   });
-
-  return prefix + str;
-};
-
-/** init the api after that flash player has been setup - called by the callback
-    defined by the embedded flash player 
-
-IriSP.PopcornReplacement.player.prototype.__initApi = function() {
-  this.trigger("loadedmetadata"); // we've done more than loading metadata of course,
-                                                      // but popcorn doesn't need to know more.
-  this.media.muted = this.playerFns.getMute();
-  /* some programmed segments are supposed to be run at the beginning 
-  var i = 0;
-  for(i = 0; i < this.__codes.length; i++) {
-    var c = this.__codes[i];
-    if (0 == c.start) {
-      c.onStart();
-    }
-    
-    if (0 == c.end) {
-      c.onEnd();
-    }
-  }
-  
-};
-*/
-
-IriSP.PopcornReplacement.player.prototype.currentTime = function(time) {
-  if (typeof(time) === "undefined") {        
-      return this.playerFns.getPosition();            
-  } else {
-     var currentTime = +time;
-     this.playerFns.seek(currentTime);              
-     return currentTime;
-  }
-};
-
-IriSP.PopcornReplacement.player.prototype.play = function() {
-  this.media.paused = false;
-  this.trigger("play");
-  this.playerFns.play();
-};
-    
-IriSP.PopcornReplacement.player.prototype.pause = function() {
-    this.media.paused = true;
-    this.trigger("pause");
-    this.playerFns.pause();
-};
-
-IriSP.PopcornReplacement.player.prototype.muted = function(val) {
-  if (typeof(val) !== "undefined") {
-
-    if (this.playerFns.getMute() !== val) {
-      if (val) {
-        this.playerFns.setMute(true);
-        this.media.muted = true;
-      } else {
-        this.playerFns.setMute(false);
-        this.media.muted = false;
-      }
-
-      this.trigger( "volumechange" );
-    }
-    
-    return this.playerFns.getMute();
-  } else {
-    return this.playerFns.getMute();
-  }
-};
-
-IriSP.PopcornReplacement.player.prototype.volume = function(val) {
-    if (typeof this.playerFns.getVolume == "undefined" || typeof this.playerFns.setVolume == "undefined") {
-        return false;
-    }
-    var _vol = this.playerFns.getVolume();
-    if (typeof(val) !== "undefined" && parseFloat(val) !== NaN) {
-        val = Math.max(0, Math.min(1, val));
-        if (parseFloat(val) != parseFloat(_vol)) {
-            this.playerFns.setVolume(val);
-            this.trigger("volumechange");
-            _vol = this.playerFns.getVolume();
-        }
-    }
-    return _vol;
-};
-
-IriSP.PopcornReplacement.player.prototype.mute = function() {
-    this.muted(true);
-}
-
-IriSP.PopcornReplacement.player.prototype.unmute = function() {
-    this.muted(false);
-}
-
-
-IriSP.PopcornReplacement.player.prototype.roundTime = function() {
-  var currentTime = this.currentTime();
-  return Math.round(currentTime);
-};/* TODO: Separate Project-specific data from Source */
+/* TODO: Separate Project-specific data from Source */
 
 /* model.js is where data is stored in a standard form, whatever the serializer */
 
@@ -663,6 +320,7 @@
     Array.call(this);
     this.directory = _directory;
     this.idIndex = [];
+    this.__events = {};
     if (typeof _directory == "undefined") {
         console.trace();
         throw "Error : new IriSP.Model.List(directory): directory is undefined";
@@ -836,6 +494,20 @@
     });
 }
 
+IriSP.Model.List.prototype.on = function(_event, _callback) {
+    if (typeof this.__events[_event] === "undefined") {
+        this.__events[_event] = [];
+    }
+    this.__events[_event].push(_callback);
+}
+
+IriSP.Model.List.prototype.trigger = function(_event, _data) {
+    var _list = this;
+    IriSP._(this.__events[_event]).each(function(_callback) {
+        _callback.call(_list, _data);
+    });
+}
+
 /* A simple time management object, that helps converting millisecs to seconds and strings,
  * without the clumsiness of the original Date object.
  */
@@ -926,7 +598,7 @@
         this.contents = new IriSP.Model.List(this.source.directory);
         this.contents.addIds(this.id);
     } else {
-        this.contents = this.source.directory.getElement(this.id);
+        this.contents = this.source.getElement(this.id);
     }
     
 }
@@ -960,6 +632,7 @@
     this.id = _id;
     this.title = "";
     this.description = "";
+    this.__events = {}
     this.source.directory.addElement(this);
 }
 
@@ -986,6 +659,20 @@
     });
 }
 
+IriSP.Model.Element.prototype.on = function(_event, _callback) {
+    if (typeof this.__events[_event] === "undefined") {
+        this.__events[_event] = [];
+    }
+    this.__events[_event].push(_callback);
+}
+
+IriSP.Model.Element.prototype.trigger = function(_event, _data) {
+    var _element = this;
+    IriSP._(this.__events[_event]).each(function(_callback) {
+        _callback.call(_element, _data);
+    });
+}
+
 /* */
 
 IriSP.Model.Media = function(_id, _source) {
@@ -993,10 +680,42 @@
     this.elementType = 'media';
     this.duration = new IriSP.Model.Time();
     this.video = '';
+    var _this = this;
+    this.on("timeupdate", function(_time) {
+        _this.getAnnotations().filter(function(_a) {
+            return (_a.end <= _time || _a.begin > _time) && _a.playing
+        }).forEach(function(_a) {
+            _a.playing = false;
+            _a.trigger("leave");
+        });
+        _this.getAnnotations().filter(function(_a) {
+            return _a.begin <= _time && _a.end > _time && !_a.playing
+        }).forEach(function(_a) {
+            _a.playing = true;
+            _a.trigger("enter");
+        });
+    });
 }
 
 IriSP.Model.Media.prototype = new IriSP.Model.Element();
 
+/* Default functions to be overriden by players */
+
+IriSP.Model.Media.prototype.getCurrentTime = function() {  return new IriSP.Model.Time(0); }
+
+IriSP.Model.Media.prototype.getVolume = function() { return .5; }
+
+IriSP.Model.Media.prototype.getPaused = function() { return true; }
+
+IriSP.Model.Media.prototype.getMuted = function() { return false; }
+
+IriSP.Model.Media.prototype.setCurrentTime
+    = IriSP.Model.Media.prototype.setVolume
+    = IriSP.Model.Media.prototype.setMuted
+    = IriSP.Model.Media.prototype.play
+    = IriSP.Model.Media.prototype.pause
+    = function() {}
+    
 IriSP.Model.Media.prototype.setDuration = function(_durationMs) {
     this.duration.setMilliseconds(_durationMs);
 }
@@ -1050,6 +769,11 @@
     this.elementType = 'annotation';
     this.begin = new IriSP.Model.Time();
     this.end = new IriSP.Model.Time();
+    this.playing = false;
+    var _this = this;
+    this.on("click", function() {
+        _this.getMedia().setCurrentTime(_this.begin);
+    });
 }
 
 IriSP.Model.Annotation.prototype = new IriSP.Model.Element(null);
@@ -1105,6 +829,10 @@
     this.title = this.annotation.title;
     this.description = this.annotation.description;
     this.color = this.annotation.color;
+    var _this = this;
+    this.on("click", function() {
+        _mashup.setCurrentTime(_this.begin);
+    });
 }
 
 IriSP.Model.MashedAnnotation.prototype = new IriSP.Model.Element(null);
@@ -1125,6 +853,10 @@
     return this.annotation.getTags().getTitles();
 }
 
+IriSP.Model.MashedAnnotation.prototype.getDuration = function() {
+    return this.annotation.getDuration();
+}
+
 /* */
 
 IriSP.Model.Mashup = function(_id, _source) {
@@ -1133,6 +865,30 @@
     this.duration = new IriSP.Model.Time();
     this.segments = new IriSP.Model.List(_source.directory);
     this.medias = new IriSP.Model.List(_source.directory);
+    var _currentMedia = null;
+    var _this = this;
+    this.on("timeupdate", function(_time) {
+        _this.getAnnotations().filter(function(_a) {
+            return (_a.end <= _time || _a.begin > _time) && _a.playing
+        }).forEach(function(_a) {
+            _a.playing = false;
+            _a.trigger("leave");
+        });
+        _this.getAnnotations().filter(function(_a) {
+            return _a.begin <= _time && _a.end > _time && !_a.playing
+        }).forEach(function(_a) {
+            _a.playing = true;
+            _a.trigger("enter");
+            var _m = _a.getMedia();
+            if (_m !== _currentMedia) {
+                if (_currentMedia) {
+                    _currentMedia.trigger("leave");
+                }
+                _m.trigger("enter");
+                _currentMedia = _m;
+            }
+        });
+    });
 }
 
 IriSP.Model.Mashup.prototype = new IriSP.Model.Element();
@@ -1190,6 +946,23 @@
     }
 }
 
+/* Default functions to be overriden by players */
+
+IriSP.Model.Mashup.prototype.getCurrentTime = function() { return new IriSP.Model.Time(0); }
+
+IriSP.Model.Mashup.prototype.getVolume = function() { return .5; }
+
+IriSP.Model.Mashup.prototype.getPaused = function() { return true; }
+
+IriSP.Model.Mashup.prototype.getMuted = function() { return false; }
+
+IriSP.Model.Mashup.prototype.setCurrentTime
+    = IriSP.Model.Mashup.prototype.setVolume
+    = IriSP.Model.Mashup.prototype.setMuted
+    = IriSP.Model.Mashup.prototype.play
+    = IriSP.Model.Mashup.prototype.pause
+    = function() {}
+
 /* */
 
 IriSP.Model.Source = function(_config) {
@@ -1311,6 +1084,23 @@
     }
 }
 
+IriSP.Model.Source.prototype.getCurrentMedia = function(_opts) {
+    if (typeof this.currentMedia === "undefined") {
+        if (_opts.is_mashup) {
+            var _mashups = this.getMashups();
+            if (_mashups.length) {
+                this.currentMedia = _mashups[0];
+            }
+        } else {
+            var _medias = this.getMedias();
+            if (_medias.length) {
+                _media = _medias[0];
+            }
+        }
+    }
+    return this.currentMedia;
+}
+
 IriSP.Model.Source.prototype.merge = function(_source) {
     var _this = this;
     _source.forEach(function(_value, _key) {
@@ -1392,7 +1182,14 @@
         jwPlayerSWF : "player.swf",
         json : "json2.js",
         zeroClipboardJs: "ZeroClipboard.js",
-        zeroClipboardSwf: "ZeroClipboard.swf"
+        zeroClipboardSwf: "ZeroClipboard.swf",
+        backbone: "backbone.js",
+        backboneRelational: "backbone-relational.js",
+        paper: "paper.js",
+        jqueryMousewheel: "jquery.mousewheel.min.js",
+        renkanPublish: "renkan-publish.js",
+        processing: "processing-1.3.6.min.js",
+        recordMicSwf: "record_mic.swf"
     },
     locations : {
         // use to define locations outside default_dir
@@ -1413,6 +1210,27 @@
 IriSP.widgetsDir = 'widgets';
 
 IriSP.widgetsRequirements = {
+    PopcornPlayer: {
+        noCss: true,
+        requires: [ "popcorn" ]
+    },
+    JwpPlayer: {
+        noCss: true,
+        requires: [ "jwplayer" ]
+    },
+    DailymotionPlayer: {
+        noCss: true,
+        requires: [ "swfObject" ]
+    },
+    AutoPlayer: {
+        noCss: true
+    },
+    MashupPlayer: {
+        noCss: true
+    },
+    AnnotationsList: {
+        requires: [ "jwplayer" ]
+    },
     Sparkline: {
         noCss: true,
         requires: [ "raphael" ]
@@ -1428,11 +1246,17 @@
         noCss: true,
         requires: [ "tracemanager" ]
     },
-    SlideShare: {
+    Slideshare: {
         requires: [ "swfObject" ]
     },
     Social: {
         requires: [ "zeroClipboardJs" ]
+    },
+    Renkan: {
+        requires: [ "backbone", "backboneRelational", "paper", "jqueryMousewheel", "renkanPublish" ]
+    },
+    KnowledgeConcierge: {
+        requires: [ "processing" ]
     }
 }
 
@@ -1469,7 +1293,7 @@
     
     /* Setting all the configuration options */
     var _type = config.type,
-        _config = IriSP._.defaults({}, config, player.config.gui.default_options, this.defaults),
+        _config = IriSP._.defaults({}, config, player.config.default_options, this.defaults),
         _this = this;
     
     IriSP._(_config).forEach(function(_value, _key) {
@@ -1477,7 +1301,7 @@
     });
     
     if (typeof this.width === "undefined") {
-        this.width = player.config.gui.width;
+        this.width = player.config.width;
     }
     
     /* Setting this.player at the end in case it's been overriden
@@ -1490,6 +1314,15 @@
     
     /* Call draw when loaded */
     this.source.onLoad(function() {
+        if (_this.media_id) {
+            _this.media = this.getElement(_this.media_id);
+        } else {
+            var _mediaopts = {
+                is_mashup: _this.is_mashup || false
+            }
+            _this.media = this.getCurrentMedia(_mediaopts);
+        }
+        
         _this.draw();
     });
    
@@ -1531,27 +1364,41 @@
             return _function.apply(_this, Array.prototype.slice.call(arguments, 0));
         }
     } else {
-        console.log("Error, Unknown function IriSP." + this.type + "." + _name)
+        console.log("Error, Unknown function IriSP.Widgets." + this.type + "." + _name)
+    }
+}
+
+IriSP.Widgets.Widget.prototype.getFunctionOrName = function(_functionOrName) {
+    switch (typeof _functionOrName) {
+        case "function":
+            return _functionOrName;
+        case "string":
+            return this.functionWrapper(_functionOrName);
+        default:
+            return undefined;
     }
 }
 
-IriSP.Widgets.Widget.prototype.bindPopcorn = function(_popcornEvent, _functionName) {
-    this.player.popcorn.listen(_popcornEvent, this.functionWrapper(_functionName))
+IriSP.Widgets.Widget.prototype.onMdpEvent = function(_eventName, _functionOrName) {
+    this.player.on(_eventName, this.getFunctionOrName(_functionOrName));
+}
+
+IriSP.Widgets.Widget.prototype.onMediaEvent = function(_eventName, _functionOrName) {
+    this.media.on(_eventName, this.getFunctionOrName(_functionOrName));
 }
 
 IriSP.Widgets.Widget.prototype.getWidgetAnnotations = function() {
-    var _curmedia = this.source.currentMedia;
-    return typeof this.annotation_type !== "undefined" && this.annotation_type ? _curmedia.getAnnotationsByTypeTitle(this.annotation_type) : _curmedia.getAnnotations();
+    return typeof this.annotation_type !== "undefined" && this.annotation_type ? this.media.getAnnotationsByTypeTitle(this.annotation_type) : this.media.getAnnotations();
 }
 
 IriSP.Widgets.Widget.prototype.getWidgetAnnotationsAtTime = function() {
-    var _time = Math.floor(this.player.popcorn.currentTime() * 1000);
+    var _time = this.media.getCurrentTime();
     return this.getWidgetAnnotations().filter(function(_annotation) {
         return _annotation.begin <= _time && _annotation.end > _time;
     });
 }
 
-IriSP.Widgets.Widget.prototype.insertSubwidget = function(_selector, _propname, _widgetoptions) {
+IriSP.Widgets.Widget.prototype.insertSubwidget = function(_selector, _widgetoptions, _propname) {
     var _id = _selector.attr("id"),
         _this = this,
         _type = _widgetoptions.type,
@@ -1568,7 +1415,9 @@
     }
     $L.wait(function() {
         _this.player.loadWidget(_widgetoptions, function(_widget) {
-            _this[_propname] = _widget;
+            if (_propname) {
+                _this[_propname] = _widget;
+            }
         });
     });
 }
@@ -1578,641 +1427,6 @@
  */
 IriSP.Widgets.Widget.prototype.draw = function() {
     /* implemented by "sub-classes" */
-};/* To wrap a player the develop should create a new class derived from
-the IriSP.PopcornReplacement.player and defining the correct functions */
-
-/** allocine player wrapper */
-IriSP.PopcornReplacement.allocine = function(container, options) {
-//    console.log("Calling allocine player");
-    /* appel du parent pour initialiser les structures communes à tous les players */
-    IriSP.PopcornReplacement.player.call(this, container, options);   
-    
-    var _this = this;
-
-    /* Définition des fonctions de l'API -  */
-
-    this.playerFns = {
-        play : function() {
-            return _this.apiCall("play");
-        },
-        pause : function() {
-            return _this.apiCall("pause");
-        },
-        getPosition : function() {
-            return _this.apiCall("getSeek","return") || 0;
-        },
-        seek : function(pos) {
-            return _this.apiCall("seek",pos);
-        },
-        getMute : function() {
-            return _this.apiCall("getMute","return");
-        },
-        setMute : function(p) {
-            return _this.apiCall("setMute", p);
-        }
-    }
-
-    window.onReady = function() {
-        _this.ready();
-    };
-    window.onAllocineStateChange = function(_state) {
-        _this.stateHandler(_state)
-    }
-    window.onTime = function(_progress) {
-        _this.progressHandler(_progress)
-    };
-    
-    var _flashVars = {
-        "streamFMS" : true,
-        "adVast" : false,
-        "lg" : "fr_cinecast",
-        "autoPlay" : options.autoPlay,
-        "directVideoTitle" : "",
-        "urlAcData" : options.urlAcData,
-        "directVideoPath" : options.video,
-        "host" : "http://allocine.fr"
-    }
-    
-    if (typeof IriSP.__jsonMetadata["medias"][0].meta == "object" && typeof IriSP.__jsonMetadata["medias"][0].meta.subtitles == "string") {
-        _flashVars.subTitlePath = IriSP.__jsonMetadata["medias"][0].meta.subtitles;
-    }
-    
-
-    var params = {
-        "allowScriptAccess" : "always",
-        "wmode": "opaque",
-        "flashvars" : IriSP.jQuery.param(_flashVars),
-        "allowfullscreen" : true
-    };
-    var atts = {
-        id : this.container
-    };
-    swfobject.embedSWF(options.acPlayerUrl, this.container, options.width, options.height, "10", null, null, params, atts);
-
-};
-
-IriSP.PopcornReplacement.allocine.prototype = new IriSP.PopcornReplacement.player("", {});
-
-IriSP.PopcornReplacement.allocine.prototype.ready = function() {
-    this.player = document.getElementById(this.container);
-    this.player.addEventListener("onStateChange", "onAllocineStateChange");
-    this.player.cueVideoByUrl(this._options.video);
-    this.trigger("loadedmetadata");
-};
-
-IriSP.PopcornReplacement.allocine.prototype.progressHandler = function(progressInfo) {
-    this.trigger("timeupdate");
-}
-
-
-IriSP.PopcornReplacement.allocine.prototype.apiCall = function(_method, _arg) {
-    if (this.player) {
-        try {
-            if (typeof _arg == "undefined") {
-                return this.player.sendToActionScript(_method);
-            } else {
-                return this.player.sendToActionScript(_method, _arg);
-            }
-        } catch(e) {
-            console.error('Exception while requesting AcPlayer for "' + _method + (typeof _arg == "undefined" ? '' : '" with argument "' + _arg ) + '"\n', e);
-            return false;
-        }
-    } else {
-        return false;
-    }
-}
-
-IriSP.PopcornReplacement.allocine.prototype.stateHandler = function(state) {
-    switch(state) {
-        case 1:
-            this.trigger("play");
-            break;
-
-        case 2:
-            this.trigger("pause");
-            break;
-
-        case 3:
-            this.trigger("seeked");
-            break;
-    }
-    
-};/* 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.dailymotion = function(container, options) {
-    /* Appel du constructeur de la classe parente */
-    IriSP.PopcornReplacement.player.call(this, container, options);   
-    
-    var _this = this;
-
-    /* Définition des fonctions de commande :
-     this.playerFns.play, .pause, .getPosition, .seek,
-     .getMute, .setMute, .getVolume, .setVolume
-     doivent être rattachés aux fonctions du player
-     * */
-
-    this.playerFns = {
-        play : function() {
-            if (_this.player) {
-                return _this.player.playVideo();
-            } else {
-                return false;
-            }
-        },
-        pause : function() {
-            if (_this.player) {
-                return _this.player.pauseVideo();
-            } else {
-                return false;
-            }
-        },
-        getPosition : function() {
-            if (_this.player) {
-                return _this.player.getCurrentTime();
-            } else {
-                return 0;
-            }
-        },
-        seek : function(pos) {
-            if (_this.player) {
-                return _this.player.seekTo(pos);
-            } else {
-                return false;
-            }
-        },
-        getMute : function() {
-            if (_this.player) {
-                return _this.player.isMuted();
-            } else {
-                return false;
-            }
-        },
-        setMute : function(p) {
-            if (_this.player) {
-                if (p) {
-                    _this.player.mute();
-                }
-                else {
-                    _this.player.unMute();
-                }
-            }
-        },
-        getVolume : function() {
-            if (_this.player) {
-                return _this.player.getVolume() / 100;
-            } else {
-                return false;
-            }
-        },
-        setVolume : function(p) {
-            if (_this.player) {
-                _this.player.setVolume(Math.floor(100 * p));
-            }
-        },
-    }
-    
-    /* Dailymotion utilise un système de fonctions référencées dans
-     * des variables globales pour la gestion des événements.
-     */
-    
-    window.onDailymotionPlayerReady = function() {
-        _this.onReady();
-    };
-    window.onDailymotionStateChange = function(_state) {
-        _this.onStateChange(_state);
-    }
-    window.onDailymotionVideoProgress = function(_progress) {
-        _this.onProgress(_progress);
-    }
-
-    var params = {
-        "allowScriptAccess" : "always",
-        "wmode": "opaque"
-    };
-    var atts = {
-        id : this.container
-    };
-    swfobject.embedSWF("http://www.dailymotion.com/swf?chromeless=1&enableApi=1", this.container, options.width, options.height, "8", null, null, params, atts);
-
-};
-
-IriSP.PopcornReplacement.dailymotion.prototype = new IriSP.PopcornReplacement.player("", {});
-
-IriSP.PopcornReplacement.dailymotion.prototype.onReady = function() {
-    
-    this.player = document.getElementById(this.container);
-    
-    this.player.addEventListener("onStateChange", "onDailymotionStateChange");
-    this.player.addEventListener("onVideoProgress", "onDailymotionVideoProgress");
-    this.player.cueVideoByUrl(this._options.video);
-    
-    this.trigger("loadedmetadata");
-};
-
-IriSP.PopcornReplacement.dailymotion.prototype.onProgress = function(progressInfo) {
-    this.trigger("timeupdate");
-}
-
-IriSP.PopcornReplacement.dailymotion.prototype.onStateChange = function(state) {
-    
-    switch(state) {
-        case 1:
-            this.trigger("play");
-            break;
-
-        case 2:
-            this.trigger("pause");
-            break;
-
-        case 3:
-            this.trigger("seeked");
-            break;
-    }
-    
-};/* 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.jwplayer = function(container, options) {
-    /* appel du parent pour initialiser les structures communes à tous les players */
-    IriSP.PopcornReplacement.player.call(this, container, options);
-    
-    if (options.autostart) {
-        this.media.paused = false;
-        this.trigger("play");
-    }
- 
-    var _player = jwplayer(this.container),
-        _this = this,
-        _seekPause = false;
-  
-  /* Définition des fonctions de l'API -  */
-    this.playerFns = {
-        play: function() { return _player.play(true); },
-        pause: function() { return _player.pause(true); },
-        getPosition: function() { return _player.getPosition(); },
-        seek: function(pos) {
-            _seekPause = _this.media.paused;
-            return _player.seek(pos);
-        },
-        getMute: function() { return _player.getMute() },
-        setMute: function(p) { return _player.setMute(p); },
-        getVolume: function() { return _player.getVolume() / 100; },
-        setVolume: function(p) { return _player.setVolume(Math.floor(100*p)); }
-    }
-
-    options.events = {
-        onReady:  function() {
-            _this.trigger("loadedmetadata");
-        },
-        onTime:   function() {
-            if (_seekPause) {
-                _player.pause(true);
-                _seekPause = false;
-            } else {
-                if (_this.media.paused && _player.getState() === "PLAYING") {
-                    _this.media.paused = false;
-                    _this.trigger("play");
-                }
-            }
-            _this.trigger("timeupdate");
-        },
-        onPlay:   function() {
-            if (!_seekPause) {
-                _this.media.paused = false;
-                _this.trigger("play");
-            }
-        },
-        onPause:  function() {
-            _this.media.paused = true;
-            _this.trigger("pause");
-        },
-        onSeek:   function() {
-            _this.trigger("seeked");
-        }
-    };
-
-    _player.setup(options);
-};
-
-IriSP.PopcornReplacement.jwplayer.prototype = new IriSP.PopcornReplacement.player("", {});
-/* 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>'),
-            _videoUrl = _media.video;
-        if (typeof options.url_transform === "function") {
-            _videoUrl = options.url_transform(_videoUrl);
-        }
-        
-        _videoEl
-            .attr({
-                src : _videoUrl,
-                id : _tmpId,
-                width : _w,
-                height : _h
-            })
-            .css({
-                position: "absolute",
-                top: 0,
-                left: 0
-            });
-
-        _this.$.append(_videoEl);
-        _media.videoEl = _videoEl;
-        _media.popcorn = Popcorn("#" + _tmpId);
-        _media.loadedMetadata = false;
-        _media.popcorn.on("loadedmetadata", function() {
-            _media.loadedMetadata = true;
-            var _allLoaded = true;
-            for (var _i = 0; _i < metadata.currentMedia.medias.length; _i++) {
-                _allLoaded = _allLoaded && metadata.currentMedia.medias[_i].loadedMetadata;
-            }
-            if (_allLoaded) {
-                _this.changeCurrentAnnotation();
-                _this.trigger("loadedmetadata");
-            }
-        });
-        _media.popcorn.on("timeupdate", function() {
-            if (!_this.media.paused && _media === _this.currentMedia) {
-                var _time = Math.round( 1000 * _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 / 1000);
-//                        _status += " before segment";
-                    }
-                } else {
-                    _this.timecode = _this.segmentEnd - _this.timedelta;
-                    _media.popcorn.pause();
-                    _this.changeCurrentAnnotation();
-//                    _status += " after segment";
-                }
-//                _status += " (" + _this.segmentBegin + " to " + _this.segmentEnd + ")" + ", 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 / 1000;
-        },
-        seek: function(pos) {
-            _this.timecode = Math.round(pos * 1000);
-            _this.changeCurrentAnnotation();
-        },
-        getMute: function() {
-            var _res = (
-                typeof _this.currentMedia !== "undefined"
-                ? _this.currentMedia.popcorn.muted()
-                : false
-            );
-            return _res;
-        },
-        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() {
-            var _res = (
-                typeof _this.currentMedia !== "undefined"
-                ? _this.currentMedia.popcorn.volume()
-                : .5
-            );
-            return _res;
-        },
-        setVolume: function(_vol) {
-            for (var _i = 0; _i < _this.mashup.medias.length; _i++) {
-                _this.mashup.medias[_i].popcorn.volume(_vol);
-            }
-        }
-    }
-    
-};
-
-IriSP.PopcornReplacement.htmlMashup.prototype = new IriSP.PopcornReplacement.player("", {});
-
-IriSP.PopcornReplacement.htmlMashup.prototype.changeCurrentAnnotation = function() {
-    var _annotation = this.mashup.getAnnotationAtTime( this.timecode );
-    if (typeof _annotation == "undefined") {
-        if (typeof this.currentMedia !== "undefined") {
-            this.currentMedia.popcorn.pause();
-            if (!this.media.paused) {
-                this.media.paused = true;
-                this.trigger("pause");
-            }
-        }
-        return;
-    }
-    if (_annotation !== this.currentAnnotation) {
-        this.currentAnnotation = _annotation;
-        this.segmentBegin = this.currentAnnotation.annotation.begin.milliseconds;
-        this.segmentEnd = this.currentAnnotation.annotation.end.milliseconds;
-        this.timedelta = this.segmentBegin - this.currentAnnotation.begin.milliseconds;
-        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();
-            }
-        }
-/* PRELOADING */
-        var _this = this,
-            _preloadedMedias = [],
-            _toPreload = this.mashup.getAnnotations().filter(function(_a) {
-            return (_a.begin >= _this.currentAnnotation.end && _a.getMedia().id !== _this.currentMedia.id);
-        });
-        IriSP._(_toPreload).each(function(_a) {
-            var _media = _a.getMedia();
-            if (IriSP._(_preloadedMedias).indexOf(_media.id) === -1) {
-                _preloadedMedias.push(_media.id);
-                _media.popcorn.currentTime(_a.annotation.begin.getSeconds());
-                //console.log("Preloading ", _media.id, " at t=", _a.annotation.begin.getSeconds());
-            }
-        });
-        
-//        console.log("Changed segment: media="+ this.currentMedia.id + ", from=" + this.segmentBegin + " to=" + this.segmentEnd +", timedelta = ", this.timedelta)
-//    } else {
-//        console.log("changeCurrentAnnotation called, but segment hasn't changed");
-    }
-    if (this.currentMedia.popcorn.readyState()) {
-        this.currentMedia.popcorn.currentTime( (this.timecode + this.timedelta) / 1000);
-        this.trigger("timeupdate");
-    }
-    if (!this.media.paused) {
-        this.currentMedia.popcorn.play();
-    }
-}
-/* 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.mashup = function(container, options) {
-    /* Appel du constructeur de la classe parente */
-    IriSP.PopcornReplacement.player.call(this, container, options);   
-
-    var _this = this;
-
-    /* Définition des fonctions de commande :
-     this.playerFns.play, .pause, .getPosition, .seek,
-     .getMute, .setMute, .getVolume, .setVolume
-     doivent être rattachés aux fonctions du player
-     * */
-
-    this.playerFns = {
-        play : function() {
-            if (_this.player) {
-                return _this.player.playVideo();
-            } else {
-                return false;
-            }
-        },
-        pause : function() {
-            if (_this.player) {
-                return _this.player.pauseVideo();
-            } else {
-                return false;
-            }
-        },
-        getPosition : function() {
-            if (_this.player) {
-                return _this.player.getCurrentTime();
-            } else {
-                return 0;
-            }
-        },
-        seek : function(pos) {
-            if (_this.player) {
-                return _this.player.seekTo(pos);
-            } else {
-                return false;
-            }
-        },
-        getMute : function() {
-            if (_this.player) {
-                return _this.player.isMuted();
-            } else {
-                return false;
-            }
-        },
-        setMute : function(p) {
-            if (_this.player) {
-                if (p) {
-                    _this.player.mute();
-                }
-                else {
-                    _this.player.unMute();
-                }
-            }
-        },
-        getVolume : function() {
-            if (_this.player) {
-                return _this.player.getVolume() / 2;
-            } else {
-                return false;
-            }
-        },
-        setVolume : function(p) {
-            if (_this.player) {
-                _this.player.setVolume(Math.floor(2 * p));
-            }
-        },
-    }
-    
-    /* Dailymotion utilise un système de fonctions référencées dans
-     * des variables globales pour la gestion des événements.
-     */
-    
-    window.onBabPlayerReady = function() {
-        _this.onReady();
-    };
-    window.onBabStateChange = function(_state) {
-        _this.onStateChange(_state);
-    }
-    window.onBabVideoProgress = function(_progress) {
-        _this.onProgress(_progress);
-    }
-
-    var params = {
-        allowScriptAccess : "always",
-        wmode: "transparent",
-        quality: "high",
-        menu: true,
-        bgcolor: "#869ca7"
-    };
-    var atts = {
-        id : this.container
-    };
-    var flashvars = {
-        urlData: options.mashup_xml
-    };
-    swfobject.embedSWF(options.mashup_swf, this.container, options.width, options.height, "8", null, flashvars, params, atts);
-
-};
-
-IriSP.PopcornReplacement.mashup.prototype = new IriSP.PopcornReplacement.player("", {});
-
-IriSP.PopcornReplacement.mashup.prototype.onReady = function() {
-    this.player = document.getElementById(this.container);
-    this.trigger("loadedmetadata");
-};
-
-IriSP.PopcornReplacement.mashup.prototype.onProgress = function(progressInfo) {
-    this.trigger("timeupdate");
-}
-
-IriSP.PopcornReplacement.mashup.prototype.onStateChange = function(state) {
-
-    switch(state) {
-        case 1:
-            this.trigger("play");
-            break;
-
-        case 2:
-            this.trigger("pause");
-            break;
-
-        case 3:
-            this.trigger("seeked");
-            break;
-    }
-
 };/* LDT Platform Serializer */
 
 if (typeof IriSP.serializers === "undefined") {
@@ -2423,7 +1637,7 @@
         }
         
         if (typeof _data.meta !== "undefined" && typeof _data.meta.main_media !== "undefined" && typeof _data.meta.main_media["id-ref"] !== "undefined") {
-            _source.mainMedia = _data.meta.main_media["id-ref"];
+            _source.currentMedia = _source.getElement(_data.meta.main_media["id-ref"]);
         }
     }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/metadataplayer/metadataplayer/MashupPlayer.js	Thu Sep 27 12:23:41 2012 +0200
@@ -0,0 +1,215 @@
+IriSP.Widgets.MashupPlayer = function(player, config) {
+    IriSP.Widgets.Widget.call(this, player, config);
+    this.is_mashup = true;
+};
+
+IriSP.Widgets.MashupPlayer.prototype = new IriSP.Widgets.Widget();
+
+/* A Popcorn-based player for HTML5 Video, Youtube and Vimeo */
+
+IriSP.Widgets.MashupPlayer.prototype.defaults = {
+    aspect_ratio: 14/9,
+    split_screen: false,
+    player_type: "PopcornPlayer"
+}
+
+IriSP.Widgets.MashupPlayer.prototype.draw = function() {
+    var _this = this,
+        _mashup = this.media,
+        _pauseState = true,
+        _currentMedia = null,
+        _currentAnnotation = null,
+        _segmentBegin,
+        _segmentEnd,
+        _timecode = 0,
+        _timedelta;
+    
+    function changeCurrentAnnotation() {
+        var _annotation = _mashup.getAnnotationAtTime( _timecode );
+        if (typeof _annotation === "undefined") {
+            if (_currentMedia) {
+                _currentMedia.pause();
+                if (!_pauseState) {
+                    _pauseState = true;
+                    _mashup.trigger("pause");
+                }
+            }
+            return;
+        }
+        if (_annotation !== _currentAnnotation) {
+            _currentAnnotation = _annotation;
+            _segmentBegin = _currentAnnotation.annotation.begin.milliseconds;
+            _segmentEnd = _currentAnnotation.annotation.end.milliseconds;
+            _timedelta = _segmentBegin - _currentAnnotation.begin.milliseconds;
+            _currentMedia = _currentAnnotation.getMedia();
+            
+            for (var _i = 0; _i < _mashup.medias.length; _i++) {
+                if (_mashup.medias[_i].id !== _currentMedia.id) {
+                    if (!_this.split_screen) {
+                        _mashup.medias[_i].hide();
+                    }
+                    _mashup.medias[_i].pause();
+                } else {
+                    _mashup.medias[_i].show();
+                }
+            }
+            
+    /* PRELOADING */
+            var _preloadedMedias = [],
+                _toPreload = _mashup.getAnnotations().filter(function(_a) {
+                return (_a.begin >= _currentAnnotation.end && _a.getMedia() !== _currentMedia);
+            });
+            IriSP._(_toPreload).each(function(_a) {
+                var _media = _a.getMedia();
+                if (IriSP._(_preloadedMedias).indexOf(_media.id) === -1) {
+                    _preloadedMedias.push(_media.id);
+                    _media.setCurrentTime(_a.annotation.begin.getSeconds());
+                    //console.log("Preloading ", _media.id, " at t=", _a.annotation.begin.getSeconds());
+                }
+            });
+            
+    //        console.log("Changed segment: media="+ this.currentMedia.id + ", from=" + this.segmentBegin + " to=" + this.segmentEnd +", timedelta = ", this.timedelta)
+    //    } else {
+    //        console.log("changeCurrentAnnotation called, but segment hasn't changed");
+        }
+
+        if (!_pauseState) {
+            _currentMedia.play();
+        }
+        _currentMedia.setCurrentTime( _timecode + _timedelta);
+        _mashup.trigger("timeupdate", new IriSP.Model.Time(_timecode));
+
+    }
+    
+    if (!this.height) {
+        this.height = Math.floor(this.width/this.aspect_ratio);
+        this.$.css({
+            height: this.height
+        });
+    }
+    
+    var _grid = Math.ceil(Math.sqrt(_mashup.medias.length)),
+        _width = (this.split_screen ? this.width / _grid : this.width),
+        _height = (this.split_screen ? this.height / _grid : this.height)
+
+    IriSP._(_mashup.medias).each(function(_media, _key) {
+        var _el = IriSP.jQuery('<div class="Ldt-MashupPlayer-Media"><div class="Ldt-MashupPlayer-Subwidget"></div></div>');
+        _el.css({
+            position: "absolute",
+            top: (_this.split_screen ? _height * Math.floor(_key / _grid) : 0),
+            left: (_this.split_screen ? _width * (_key % _grid) : 0),
+            height: _height,
+            width: _width,
+            display: (_this.split_screen ? "block" : "none")
+        });
+        _this.$.append(_el);
+        
+        _this.insertSubwidget(
+            _el.find(".Ldt-MashupPlayer-Subwidget"),
+            IriSP._({
+                type: _this.player_type,
+                media_id: _media.id,
+                height: _height,
+                width: _width,
+                url_transform: _this.url_transform
+            }).extend(_this.player_options)
+        );
+        
+        _media.loadedMetadata = false;
+        _media.show = function() {
+            _el.show();
+        };
+        _media.hide = function() {
+            _el.hide();
+        };
+        _media.on("loadedmetadata", function() {
+            _media.loadedMetadata = true;
+            var _allLoaded = true;
+            for (var _i = 0; _i < _mashup.medias.length; _i++) {
+                _allLoaded = _allLoaded && _mashup.medias[_i].loadedMetadata;
+            }
+            if (_allLoaded) {
+                changeCurrentAnnotation();
+                _mashup.trigger("loadedmetadata");
+            }
+        });
+        _media.on("timeupdate", function(_time) {
+            if (!_pauseState && _media === _currentMedia) {
+//                var _status = "Timeupdate from " + _media.id + " at time " + _time;
+                if ( _time < _segmentEnd ) {
+                    if ( _time >= _segmentBegin ) {
+                        _timecode = _time - _timedelta;
+//                        _status += " within segment";
+                    } else {
+                        _timecode = _segmentBegin - _timedelta;
+                        _media.setCurrentTime(_segmentBegin);
+//                        _status += " before segment";
+                    }
+                } else {
+                    _timecode = _segmentEnd - _timedelta;
+                    _media.pause();
+                    changeCurrentAnnotation();
+//                    _status += " after segment";
+                }
+//                _status += " (" + _this.segmentBegin + " to " + _this.segmentEnd + ")" + ", translated to " + _this.timecode;
+//                console.log(_status);
+                _mashup.trigger("timeupdate", new IriSP.Model.Time(_timecode));
+            }
+        });
+        _media.on("play", function() {
+            if (_media === _currentMedia) {
+                _mashup.trigger("play");
+            }
+        });
+        _media.on("pause", function() {
+            if (_media === _currentMedia) {
+                _mashup.trigger("pause");
+            }
+        });
+    });
+  
+    _mashup.getCurrentTime = function() {
+        return new IriSP.Model.Time(_timecode);
+    }
+    _mashup.getVolume = function() {
+        return ( _currentMedia ? _currentMedia.getVolume() : .5 );
+    }
+    _mashup.getPaused = function() {
+        return _pauseState;
+    }
+    _mashup.getMuted = function() {
+        return ( _currentMedia ? _currentMedia.getMuted() : false );
+    }
+    _mashup.setCurrentTime = function(_milliseconds) {
+        _timecode = _milliseconds;
+        changeCurrentAnnotation();
+    }
+    _mashup.setVolume = function(_vol) {
+        for (var _i = 0; _i < _mashup.medias.length; _i++) {
+            _mashup.medias[_i].setVolume(_vol);
+        }
+    }
+    _mashup.mute = function() {
+        for (var _i = 0; _i < _mashup.medias.length; _i++) {
+            _mashup.medias[_i].mute();
+        }
+    }
+    _mashup.unmute = function() {
+        for (var _i = 0; _i < _mashup.medias.length; _i++) {
+            _mashup.medias[_i].unmute();
+        }
+    }
+    _mashup.play = function() {
+        _pauseState = false;
+        changeCurrentAnnotation();
+    }
+    _mashup.pause = function() {
+        _pauseState = true;
+        if (_currentMedia) {
+            _currentMedia.pause();
+        }
+    }
+    
+    changeCurrentAnnotation();
+   
+}
\ No newline at end of file
--- a/metadataplayer/metadataplayer/MediaList.js	Fri Aug 31 17:43:31 2012 +0200
+++ b/metadataplayer/metadataplayer/MediaList.js	Thu Sep 27 12:23:41 2012 +0200
@@ -49,17 +49,24 @@
     var _n = this.refresh(true);
     if (this.searchString) {
         if (_n) {
-            this.player.popcorn.trigger("IriSP.search.matchFound");
+            this.player.trigger("search.matchFound");
         } else {
-            this.player.popcorn.trigger("IriSP.search.noMatchFound");
+            this.player.trigger("search.noMatchFound");
         }
     }
 }
 
 IriSP.Widgets.MediaList.prototype.draw = function() {
-    this.bindPopcorn("timeupdate","onTimeupdate");
     this.$.addClass("Ldt-MediaListWidget")
     this.renderTemplate();
+    var _this = this;
+    if (typeof this.media.getMedias === "function") {
+        this.media.getMedias().forEach(function(_m) {
+            _m.on("enter", function() {
+                _this.redraw(_m);
+            });
+        })
+    }
     this.redraw();
 };
 
@@ -123,14 +130,3 @@
         this.$.find('.Ldt-MediaList-Other').hide();
     }
 };
-
-IriSP.Widgets.MediaList.prototype.onTimeupdate = function() {
-    var _media = this.source.currentMedia;
-    if (_media.elementType === "mashup") {
-        _media = _media.getMediaAtTime(this.player.popcorn.currentTime() * 1000);
-    }
-    if (typeof _media !== "undefined" && _media.id !== this.lastMedia) {
-        this.lastMedia = _media.id;
-        this.redraw(_media);
-    }
-}
--- a/metadataplayer/metadataplayer/Mediafragment.js	Fri Aug 31 17:43:31 2012 +0200
+++ b/metadataplayer/metadataplayer/Mediafragment.js	Thu Sep 27 12:23:41 2012 +0200
@@ -10,16 +10,22 @@
             }
         })
     };
-    this.bindPopcorn("pause","setHashToTime");
-    this.bindPopcorn("seeked","setHashToTime");
-    this.bindPopcorn("IriSP.Mediafragment.setHashToAnnotation","setHashToAnnotation");
+    this.onMdpEvent("Mediafragment.setHashToAnnotation","setHashToAnnotation");
     this.blocked = false;
 }
 
 IriSP.Widgets.Mediafragment.prototype = new IriSP.Widgets.Widget();
 
 IriSP.Widgets.Mediafragment.prototype.draw = function() {
+    this.onMediaEvent("pause","setHashToTime");
+    this.onMediaEvent("seeked","setHashToTime");
     this.goToHash();
+    var _this = this;
+    this.getWidgetAnnotations().forEach(function(_annotation) {
+        _annotation.on("click", function() {
+            _this.setHashToAnnotation(_annotation.id);
+        })
+    })
 }
 
 IriSP.Widgets.Mediafragment.prototype.setWindowHash = function(_hash) {
@@ -52,11 +58,11 @@
                 if (this.last_hash_key == "id") {
                     var _annotation = this.source.getElement(this.last_hash_value);
                     if (typeof _annotation !== "undefined") {
-                        this.player.popcorn.currentTime(_annotation.begin.getSeconds());
+                        this.media.setCurrentTime(_annotation.begin);
                     }
                 }
                 if (this.last_hash_key == "t") {
-                    this.player.popcorn.currentTime(this.last_hash_value);
+                    this.media.setCurrentTime(1000*this.last_hash_value);
                 }
                 break;
             }
@@ -68,10 +74,8 @@
     this.setHash( 'id', _annotationId );
 }
 
-IriSP.Widgets.Mediafragment.prototype.setHashToTime = function(_time) {
-    if (_time !== NaN) {
-        this.setHash( 't', this.player.popcorn.currentTime() );
-    }
+IriSP.Widgets.Mediafragment.prototype.setHashToTime = function() {
+    this.setHash( 't', this.media.getCurrentTime().getSeconds() );
 }
 
 IriSP.Widgets.Mediafragment.prototype.setHash = function(_key, _value) {
@@ -100,5 +104,5 @@
         window.clearTimeout(this.blockTimeout);
     }
     this.blocked = true;
-    this.blockTimeout = window.setTimeout(this.functionWrapper("unblock"), 1000);
+    this.blockTimeout = window.setTimeout(this.functionWrapper("unblock"), 1500);
 }
--- a/metadataplayer/metadataplayer/Polemic.js	Fri Aug 31 17:43:31 2012 +0200
+++ b/metadataplayer/metadataplayer/Polemic.js	Thu Sep 27 12:23:41 2012 +0200
@@ -64,7 +64,7 @@
             } else {
                 _el.css({
                     "background" : _el.attr("polemic-color"),
-                    "opacity" : .5
+                    "opacity" : .3
                 });
             }
         } else {
@@ -76,16 +76,16 @@
     });
     if (this.searchString) {
         if (_found) {
-            this.player.popcorn.trigger("IriSP.search.matchFound");
+            this.player.trigger("search.matchFound");
         } else {
-            this.player.popcorn.trigger("IriSP.search.noMatchFound");
+            this.player.trigger("search.noMatchFound");
         }
     }
 }
 
 IriSP.Widgets.Polemic.prototype.draw = function() {
     
-    this.bindPopcorn("timeupdate", "onTimeupdate");
+    this.onMediaEvent("timeupdate", "onTimeupdate");
     this.$zone = IriSP.jQuery('<div>');
     this.$zone.addClass("Ldt-Polemic");
     this.$.append(this.$zone);
@@ -145,39 +145,65 @@
                 position: "relative"
             });
             
-            var _x = 0,
-                _html = '';
+            var _x = 0;
             
-            function displayElement(_x, _y, _color, _id, _title, _polemic) {
-                _html += Mustache.to_html(
-                    '<div class="Ldt-Polemic-TweetDiv Ldt-TraceMe" trace-info="annotation-id:{{id}}, media-id={{media_id}}, polemic={{polemic}}" annotation-id="{{id}}" tweet-title="{{title}}" pos-x="{{posx}}" pos-y="{{top}}" polemic-color="{{color}}"'
-                    + ' style="width: {{width}}px; height: {{height}}px; top: {{top}}px; left: {{left}}px; background: {{color}}"></div>',
+            function displayAnnotation(_elx, _ely, _pol, _col, _annotation) {
+                var _html = Mustache.to_html(
+                    '<div class="Ldt-Polemic-TweetDiv Ldt-TraceMe" trace-info="annotation-id:{{id}}, media-id={{media_id}}, polemic={{polemic}}" polemic-color="{{color}}"'
+                    + ' tweet-title="{{title}}" annotation-id="{{id}}" style="width: {{width}}px; height: {{height}}px; top: {{top}}px; left: {{left}}px; background: {{color}}"></div>',
                 {
-                    id: _id,
+                    id: _annotation.id,
                     media_id: _this.source.currentMedia.id,
-                    polemic: _polemic,
-                    title: _title,
-                    posx: Math.floor(_x + (_this.element_width - 1) / 2),
-                    left: _x,
-                    top: _y,
-                    color: _color,
+                    polemic: _pol,
+                    left: _elx,
+                    top: _ely,
+                    color: _col,
                     width: (_this.element_width-1),
-                    height: _this.element_height
+                    height: _this.element_height,
+                    title: _annotation.title
+                });
+                var _el = IriSP.jQuery(_html);
+                _el.mouseover(function() {
+                    _annotation.trigger("select");
+                }).mouseout(function() {
+                    _annotation.trigger("unselect");
+                }).click(function() {
+                    _annotation.trigger("click");
                 });
+                _annotation.on("select", function() {
+                    _this.tooltip.show(
+                        Math.floor(_elx + (_this.element_width - 1) / 2),
+                        _ely,
+                        _annotation.title,
+                        _col
+                    );
+                    _this.$tweets.each(function() {
+                        var _e = IriSP.jQuery(this);
+                        _e.css(
+                            "opacity",
+                            ( _e.attr("annotation-id") == _annotation.id ? 1 : .3 )
+                        );
+                    });
+                });
+                _annotation.on("unselect", function() {
+                    _this.tooltip.hide();
+                    _this.$tweets.css("opacity",1);
+                });
+                _this.$zone.append(_el);
             }
             
             IriSP._(_slices).forEach(function(_slice) {
                 var _y = _this.height;
                 _slice.annotations.forEach(function(_annotation) {
                     _y -= _this.element_height;
-                    displayElement(_x, _y, _this.defaultcolor, _annotation.id, _annotation.title, "none");
+                    displayAnnotation(_x, _y, "none", _this.defaultcolor, _annotation);
                 });
                 IriSP._(_slice.polemicStacks).forEach(function(_annotations, _j) {
                     var _color = _this.polemics[_j].color,
                         _polemic = _this.polemics[_j].name;
                     _annotations.forEach(function(_annotation) {
                         _y -= _this.element_height;
-                        displayElement(_x, _y, _color, _annotation.id, _annotation.title, _polemic);
+                        displayAnnotation(_x, _y, _polemic, _color, _annotation);
                     });
                 });
                 _x += _this.element_width;
@@ -187,23 +213,9 @@
             
             this.$tweets = this.$.find(".Ldt-Polemic-TweetDiv");
             
-            this.$tweets
-                .mouseover(function() {
-                    var _el = IriSP.jQuery(this);
-                    _this.tooltip.show(_el.attr("pos-x"), _el.attr("pos-y"), _el.attr("tweet-title"), _el.attr("polemic-color"));
-                })
-                .mouseout(function() {
-                    _this.tooltip.hide();
-                })
-                .click(function() {
-                    var _id = IriSP.jQuery(this).attr("annotation-id");
-                    _this.player.popcorn.trigger("IriSP.Mediafragment.setHashToAnnotation", _id);
-                    _this.player.popcorn.trigger("IriSP.Tweet.show", _id);
-                });
-            
-            this.bindPopcorn("IriSP.search", "onSearch");
-            this.bindPopcorn("IriSP.search.closed", "onSearch");
-            this.bindPopcorn("IriSP.search.cleared", "onSearch");
+            this.onMdpEvent("search", "onSearch");
+            this.onMdpEvent("search.closed", "onSearch");
+            this.onMdpEvent("search.cleared", "onSearch");
             
         } else {
             this.$zone.hide();
@@ -290,16 +302,16 @@
     
     this.$zone.click(function(_e) {
         var _x = _e.pageX - _this.$zone.offset().left;
-        _this.player.popcorn.currentTime(_this.source.getDuration().getSeconds() * _x / _this.width);
+        _this.media.setCurrentTime(_this.media.duration * _x / _this.width);
     });
     
     this.$.append('<div class="Ldt-Polemic-Tooltip"></div>');
     
-    this.insertSubwidget(this.$.find(".Ldt-Polemic-Tooltip"), "tooltip", { type: "Tooltip" });
+    this.insertSubwidget(this.$.find(".Ldt-Polemic-Tooltip"), { type: "Tooltip" }, "tooltip");
 }
 
-IriSP.Widgets.Polemic.prototype.onTimeupdate = function() {
-    var _x = Math.floor( this.width * this.player.popcorn.currentTime() / this.source.getDuration().getSeconds());
+IriSP.Widgets.Polemic.prototype.onTimeupdate = function(_time) {
+    var _x = Math.floor( this.width * _time / this.media.duration);
     this.$elapsed.css({
         width:  _x + "px"
     });
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/metadataplayer/metadataplayer/PopcornPlayer.js	Thu Sep 27 12:23:41 2012 +0200
@@ -0,0 +1,132 @@
+IriSP.Widgets.PopcornPlayer = function(player, config) {
+    IriSP.Widgets.Widget.call(this, player, config);
+};
+
+IriSP.Widgets.PopcornPlayer.prototype = new IriSP.Widgets.Widget();
+
+/* A Popcorn-based player for HTML5 Video, Youtube and Vimeo */
+
+IriSP.Widgets.PopcornPlayer.prototype.defaults = {
+    aspect_ratio: 14/9
+}
+
+IriSP.Widgets.PopcornPlayer.prototype.draw = function() {
+
+    
+    if (typeof this.video === "undefined") {
+        this.video = this.media.video;
+    }
+    
+    if (this.url_transform) {
+        this.video = this.url_transform(this.video);
+    }
+    
+    if (!this.height) {
+        this.height = Math.floor(this.width/this.aspect_ratio);
+        this.$.css({
+                height: this.height
+            });
+    }
+    
+    if (/^(https?:\/\/)?(www\.)?vimeo\.com/.test(this.video)) {
+        
+        /* VIMEO */
+        
+        var _popcorn = Popcorn.vimeo(this.container, this.video);
+        
+    } else if (/^(https?:\/\/)?(www\.)?youtube\.com/.test(this.video)) {
+        
+        /* YOUTUBE */
+       
+        var _urlparts = this.video.split(/[?&]/),
+            _params = {};
+        for (var i = 1; i < _urlparts.length; i++) {
+            var _ppart = _urlparts[i].split('=');
+            _params[_ppart[0]] = decodeURIComponent(_ppart[1]);
+        }
+        _params.controls = 0;
+        _params.modestbranding = 1;
+        _url = _urlparts[0] + '?' + IriSP.jQuery.param(_params);
+        
+        var _popcorn = Popcorn.youtube(this.container, _url);
+        
+    } else {
+        
+        /* DEFAULT HTML5 */
+        
+        var _tmpId = IriSP._.uniqueId("popcorn"),
+            _videoEl = IriSP.jQuery('<video>');
+        _videoEl.attr({
+            id : _tmpId,
+            width : this.width,
+            height : this.height
+        });
+        if(typeof this.video === "string"){
+            _videoEl.attr("src",this.video);
+        } else {
+            for (var i = 0; i < this.video.length; i++) {
+                var _srcNode = IriSP.jQuery('<source>');
+                _srcNode.attr({
+                    src: this.video[i].src,
+                    type: this.video[i].type
+                });
+                _videoEl.append(_srcNode);
+            }
+        }
+        this.$.html(_videoEl);
+        var _popcorn = Popcorn("#" + _tmpId);
+    }
+
+    // Binding functions to Popcorn
+    
+    this.media.getCurrentTime = function() {
+        return new IriSP.Model.Time(1000*_popcorn.currentTime());
+    }
+    this.media.getVolume = function() {
+        return _popcorn.volume();
+    }
+    this.media.getPaused = function() {
+        return _popcorn.media.paused;
+    }
+    this.media.getMuted = function() {
+        return _popcorn.muted();
+    }
+    this.media.setCurrentTime = function(_milliseconds) {
+        return _popcorn.currentTime(_milliseconds / 1000);
+    }
+    this.media.setVolume = function(_vol) {
+        return _popcorn.volume(_vol);
+    }
+    this.media.mute = function() {
+        return _popcorn.muted(true);
+    }
+    this.media.unmute = function() {
+        return _popcorn.muted(false);
+    }
+    this.media.play = function() {
+        return _popcorn.play();
+    }
+    this.media.pause = function() {
+        return _popcorn.pause();
+    }
+    
+    // Binding Popcorn events to media
+    
+    var _media = this.media;
+    _popcorn.on("timeupdate", function() {
+        _media.trigger("timeupdate", _media.getCurrentTime());
+    });
+    
+    function simpleEventBind(_eventname) {
+        _popcorn.on(_eventname, function() {
+            _media.trigger(_eventname);
+        });
+    }
+    
+    simpleEventBind("play");
+    simpleEventBind("pause");
+    simpleEventBind("seeked");
+    simpleEventBind("loadedmetadata");
+    simpleEventBind("volumechange");
+    
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/metadataplayer/metadataplayer/Renkan.css	Thu Sep 27 12:23:41 2012 +0200
@@ -0,0 +1,93 @@
+
+.Ldt-Renkan {
+    position: relative;
+}
+
+.Ldt-Renkan a {
+    color: #303080;
+}
+
+.Rk-ProjectList {
+    position: absolute; left: 0; top: 0; list-style: none; padding: 0; margin: 0;
+}
+
+.Rk-Project {
+    list-style: none; margin-right: 5px; padding: 5px; height: 15px; font-size: 13px; font-weight: bold;
+    float: left; border-top-left-radius: 8px; border-top-right-radius: 8px;
+    background: #505050; color: #E0E0E0; cursor: pointer;
+    background: -moz-linear-gradient(top, #333333 20%, #666666 80%);
+}
+
+.Rk-Project:hover {
+    background: -moz-linear-gradient(bottom, #333333 20%, #666666 80%);
+}
+
+.Rk-Project.active {
+    background: #505080;
+    color: #ffffc0;
+    background: -moz-linear-gradient(bottom, #333366 20%, #666699 80%);
+}
+.Rk-Render, .Rk-Canvas {
+    position: absolute; left: 0; top: 0; width: 600px; height: 500px;
+    overflow: hidden;
+}
+
+.Rk-Render {
+    top: 25px;
+    background: -moz-linear-gradient(top, rgba(180,180,180,.2) 0, rgba(255,255,255,.2) 30%, rgba(255,255,255,.2) 70%, rgba(180,180,180,.2) 100%);
+    background: -webkit-linear-gradient(top, rgba(180,180,180,.2) 0, rgba(255,255,255,.2) 30%, rgba(255,255,255,.2) 70%, rgba(180,180,180,.2) 100%);
+}
+
+.Rk-Canvas {
+    top: 0;
+    background: -moz-linear-gradient(left, rgba(180,180,180,.2) 0, rgba(255,255,255,.2) 30%, rgba(255,255,255,.2) 70%, rgba(180,180,180,.2) 100%);
+    background: -webkit-linear-gradient(left, rgba(180,180,180,.2) 0, rgba(255,255,255,.2) 30%, rgba(255,255,255,.2) 70%, rgba(180,180,180,.2) 100%);
+}
+
+.Rk-Editor {
+    position: absolute; left: 0; top: 0;
+}
+
+.Rk-CloseX {
+    float: right; cursor: pointer;
+}
+
+.Rk-Editor h2 {
+    font-size: 16px; font-weight: bold;
+}
+
+.Rk-Editor p {
+    margin: 5px 0; font-size: 12px;
+}
+
+.Rk-RelatedList {
+    list-style: none; padding: 0; margin: 0;
+}
+
+.Rk-Related {
+    list-style: none; color: #a000c0; cursor: pointer; margin: 5px 0;
+}
+
+.Rk-Related b {
+    font-weight: bolder;
+}
+
+.Rk-Related:hover {
+    text-decoration: underline;
+}
+
+.Rk-ZoomIn, .Rk-ZoomOut {
+    width: 21px; height: 20px; background: url(img/zoombuttons.png); margin: 5px; cursor: pointer;
+}
+
+.Rk-ZoomIn:hover {
+    background-position: 0 -20px;
+}
+
+.Rk-ZoomOut {
+    background-position: -21px 0;
+}
+
+.Rk-ZoomOut:hover {
+    background-position: -21px -20px;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/metadataplayer/metadataplayer/Renkan.js	Thu Sep 27 12:23:41 2012 +0200
@@ -0,0 +1,117 @@
+IriSP.Widgets.Renkan = function(player, config) {
+    IriSP.Widgets.Widget.call(this, player, config);
+};
+
+IriSP.Widgets.Renkan.prototype = new IriSP.Widgets.Widget();
+
+IriSP.Widgets.Renkan.prototype.defaults = {
+    annotation_regexp: /player\/([a-zA-Z0-9_-]+)\/.*id=([a-zA-Z0-9_-]+)/,
+    tag_regexp: /search=([^&=]+)/,
+    min_duration: 5000
+}
+
+IriSP.Widgets.Renkan.prototype.messages = {
+    "fr": {
+    },
+    "en": {
+    }
+}
+
+IriSP.Widgets.Renkan.prototype.template =
+    '<div class="Ldt-Renkan"></div>';
+
+IriSP.Widgets.Renkan.prototype.draw = function() {
+    this.renderTemplate();
+    var _id = IriSP.Model.getUID();
+    this.$.find(".Ldt-Renkan").attr("id", _id);
+    this.renkan = new Rkns.Renkan({
+        container: _id
+    });
+    if (typeof this.data === "string") {
+        this.data = [ this.data ];
+    }
+    var _this = this,
+        _list = this.getWidgetAnnotations();
+    this.node_times = [];
+    Rkns._(this.data).each(function(_url, _key) {
+        var _opts = {
+            url: _url
+        }
+        if (!_key) {
+            _opts.callback = function(_p) {
+                _this.renkan.renderProject(_p);
+            }
+        }
+        var _proj = _this.renkan.addProject(_opts);
+        _proj.on("add:nodes", function(_node) {
+            var _uri = _node.get("uri"),
+                _annmatch = _uri.match(_this.annotation_regexp);
+            if (_annmatch) {
+                var _annotations = _list.filter(function(_ann) {
+                    return _ann.getMedia().id == _annmatch[1] && _ann.id == _annmatch[2];
+                });
+                _annotations.forEach(function(_ann) {
+                    var _duration = _ann.getDuration(),
+                        _preroll = + ( _duration < _this.min_duration ) * ( _this.min_duration / 2);
+                    var _nt = {
+                        selected: false,
+                        node: _node,
+                        begin: _ann.begin - _preroll,
+                        end: _ann.end + _preroll
+                    }
+                    _this.node_times.push(_nt);
+                    _ann.on("select", function(_stop) {
+                        if (!_stop) {
+                            _node.trigger("select",true);
+                        }
+                    });
+                    _node.on("select", function(_stop) {
+                        if (!_stop) {
+                            _ann.trigger("select",true);
+                        }
+                    });
+                    _ann.on("unselect", function(_stop) {
+                        if (!_stop) {
+                            _node.trigger("unselect",true);
+                        }
+                    });
+                    _node.on("unselect", function(_stop) {
+                        _nt.selected = false;
+                        if (!_stop) {
+                            _ann.trigger("unselect",true);
+                        }
+                    });
+                    _node.on("click", function() {
+                        _this.media.setCurrentTime(_ann.begin);
+                        _this.player.trigger("Mediafragment.setHashToAnnotation", _ann.id);
+                    });
+                });
+            }
+            var _tagmatch = _uri.match(_this.tag_regexp);
+            if (_tagmatch) {
+                _node.on("select", function() {
+                    _this.player.trigger("search.triggeredSearch",_tagmatch[1]);
+                })
+                _node.on("unselect", function() {
+                    _this.player.trigger("search.cleared");
+                })
+            }
+        });
+    })
+    this.onMediaEvent("timeupdate","onTimeupdate");
+}
+
+IriSP.Widgets.Renkan.prototype.onTimeupdate = function(_time) {
+    IriSP._(this.node_times).each(function(_nt) {
+        if (_nt.begin <= _time && _nt.end >= _time) {
+            if (!_nt.selected) {
+                _nt.selected = true;
+                _nt.node.trigger("select", true);
+            }
+        } else {
+            if (_nt.selected) {
+                _nt.node.trigger("unselect", true);
+            }
+        }
+    });
+}
--- a/metadataplayer/metadataplayer/Segments.js	Fri Aug 31 17:43:31 2012 +0200
+++ b/metadataplayer/metadataplayer/Segments.js	Thu Sep 27 12:23:41 2012 +0200
@@ -1,4 +1,4 @@
-// TODO: Trigger IriSP.SegmentsWidget.click and IriSP.Mediafragment.showAnnotation
+// TODO: Trigger IriSP.SegmentsWidget.click
 
 IriSP.Widgets.Segments = function(player, config) {
     IriSP.Widgets.Widget.call(this, player, config);
@@ -13,18 +13,22 @@
 };
 
 IriSP.Widgets.Segments.prototype.template =
-    '<div class="Ldt-Segments-List">{{#segments}}'
-    + '<div class="Ldt-Segments-Segment Ldt-TraceMe" trace-info="segment-id:{{id}}, media-id:{{media_id}}" segment-id="{{id}}" segment-text="{{text}}" segment-color="{{color}}" center-pos="{{center}}" begin-seconds="{{beginseconds}}"'
-    + 'style="left:{{left}}px; width:{{width}}px; background:{{color}}"></div>'
-    + '{{/segments}}</div>'
+    '<div class="Ldt-Segments-List"></div>'
     + '<div class="Ldt-Segments-Position"></div>'
     + '<div class="Ldt-Segments-Tooltip"></div>';
 
+IriSP.Widgets.Segments.prototype.annotationTemplate =
+    '<div class="Ldt-Segments-Segment Ldt-TraceMe" trace-info="segment-id:{{id}}, media-id:{{media_id}}" segment-text="{{text}}"'
+    + 'style="left:{{left}}px; width:{{width}}px; background:{{color}}"></div>'
+
+
 IriSP.Widgets.Segments.prototype.draw = function() {
-    this.bindPopcorn("IriSP.search", "onSearch");
-    this.bindPopcorn("IriSP.search.closed", "onSearch");
-    this.bindPopcorn("IriSP.search.cleared", "onSearch");
-    this.bindPopcorn("timeupdate", "onTimeupdate");
+    this.onMdpEvent("search", "onSearch");
+    this.onMdpEvent("search.closed", "onSearch");
+    this.onMdpEvent("search.cleared", "onSearch");
+    this.onMediaEvent("timeupdate", "onTimeupdate");
+    
+    this.renderTemplate();
     
     var _list = this.getWidgetAnnotations(),
         _this = this,
@@ -34,42 +38,45 @@
         height : (this.height - 2) + "px",
         margin : "1px 0"
     });
-    this.$.append(Mustache.to_html(this.template, {
-        segments : _list.map(function(_annotation, _k) {
-            var _left = _annotation.begin * _scale,
-                _width = ( _annotation.end - _annotation.begin ) * _scale,
-                _center = _left + _width / 2,
-                _fulltext = _annotation.title + ( _annotation.description ? ( '<br/>' + _annotation.description ) : '' );
-            return {
-                text : _fulltext.replace(/(\n|\r|\r\n)/mg,' ').replace(/(^.{120,140})[\s].+$/m,'$1&hellip;'),
-                color : ( typeof _annotation.color !== "undefined" && _annotation.color ? _annotation.color : _this.colors[_k % _this.colors.length] ),
-                beginseconds : _annotation.begin.getSeconds() ,
-                left : Math.floor( _left ),
-                width : Math.floor( _width ),
-                center : Math.floor( _center ),
-                id : _annotation.id,
-                media_id : _annotation.getMedia().id
-            }
-        })
-    }));
-    this.insertSubwidget(this.$.find(".Ldt-Segments-Tooltip"), "tooltip", { type: "Tooltip" });
+    this.list_$ = this.$.find('.Ldt-Segments-List');
+    
+    _list.forEach(function(_annotation, _k) {
+        var _left = _annotation.begin * _scale,
+            _width = ( _annotation.getDuration() ) * _scale,
+            _center = Math.floor( _left + _width / 2 ),
+            _fulltext = _annotation.title + ( _annotation.description ? ( '<br/>' + _annotation.description ) : '' );
+        var _data = {
+            color : ( typeof _annotation.color !== "undefined" && _annotation.color ? _annotation.color : _this.colors[_k % _this.colors.length] ),
+            text: _fulltext.replace(/(\n|\r|\r\n)/mg,' ').replace(/(^.{120,140})[\s].+$/m,'$1&hellip;'),
+            left : Math.floor( _left ),
+            width : Math.floor( _width ),
+            id : _annotation.id,
+            media_id : _annotation.getMedia().id
+        };
+        var _html = Mustache.to_html(_this.annotationTemplate, _data),
+            _el = IriSP.jQuery(_html);
+        _el.mouseover(function() {
+                _annotation.trigger("select");
+            })
+            .mouseout(function() {
+                _annotation.trigger("unselect");
+            })
+            .click(function() {
+                _annotation.trigger("click");
+            })
+            .appendTo(_this.list_$)
+        _annotation.on("select", function() {
+            _this.$segments.removeClass("active").addClass("inactive");
+            _this.tooltip.show( _center, 0, _data.text, _data.color );
+            _el.removeClass("inactive").addClass("active");
+        });
+        _annotation.on("unselect", function() {
+            _this.tooltip.hide();
+            _this.$segments.removeClass("inactive active");
+        });
+    });
+    this.insertSubwidget(this.$.find(".Ldt-Segments-Tooltip"), { type: "Tooltip" }, "tooltip");
     this.$segments = this.$.find('.Ldt-Segments-Segment');
-    
-    this.$segments.mouseover(function() {
-        var _el = IriSP.jQuery(this);
-        _this.$segments.removeClass("active").addClass("inactive");
-        _this.tooltip.show( _el.attr("center-pos"), 0, _el.attr("segment-text"), _el.attr("segment-color"));
-        _el.removeClass("inactive").addClass("active");
-    })
-    .mouseout(function() {
-        _this.tooltip.hide();
-        _this.$segments.removeClass("inactive active");
-    })
-    .click(function() {
-        var _el = IriSP.jQuery(this);
-        _this.player.popcorn.currentTime(_el.attr("begin-seconds"));
-        _this.player.popcorn.trigger("IriSP.Mediafragment.setHashToAnnotation", _el.attr("segment-id"));
-    });
 }
 
 IriSP.Widgets.Segments.prototype.onSearch = function(searchString) {
@@ -87,17 +94,17 @@
             }
         });
         if (_found) {
-            this.player.popcorn.trigger("IriSP.search.matchFound");
+            this.player.trigger("search.matchFound");
         } else {
-            this.player.popcorn.trigger("IriSP.search.noMatchFound");
+            this.player.trigger("search.noMatchFound");
         }
     } else {
         this.$segments.removeClass("found unfound");
     }
 }
 
-IriSP.Widgets.Segments.prototype.onTimeupdate = function() {
-    var _x = Math.floor( this.width * this.player.popcorn.currentTime() / this.source.getDuration().getSeconds());
+IriSP.Widgets.Segments.prototype.onTimeupdate = function(_time) {
+    var _x = Math.floor( this.width * _time / this.media.duration);
     this.$.find('.Ldt-Segments-Position').css({
         left: _x + "px"
     })
--- a/metadataplayer/metadataplayer/Slice.css	Fri Aug 31 17:43:31 2012 +0200
+++ b/metadataplayer/metadataplayer/Slice.css	Thu Sep 27 12:23:41 2012 +0200
@@ -1,7 +1,7 @@
 /* Slider Widget */
 
 .Ldt-Slice {
-    border-radius: 0; border: none; padding: 0; margin: 2px 0 12px; background: #B6B8B8; height: 8px;
+    border-radius: 0; border: none; padding: 0; margin: 12px 0 12px; background: #B6B8B8; height: 8px;
 }
 
 .Ldt-Slice .ui-slider-handle {
--- a/metadataplayer/metadataplayer/Slice.js	Fri Aug 31 17:43:31 2012 +0200
+++ b/metadataplayer/metadataplayer/Slice.js	Thu Sep 27 12:23:41 2012 +0200
@@ -10,90 +10,72 @@
 IriSP.Widgets.Slice.prototype = new IriSP.Widgets.Widget();
 
 IriSP.Widgets.Slice.prototype.defaults = {
-    start_visible : false,
-    live_update : true,
-        /* Shall the bounds change each time
-        the Annotation Widget sends an update (true)
-        or only when "show" is triggered (false) ?
-        - true is to be recommended when the widget is permanently displayed.
-        */
-    override_bounds : true
-        /* Can the Annotation Widget bounds be overriden ? */
+    show_arrow: false
 };
 
+IriSP.Widgets.Slice.prototype.template =
+    '<div class="Ldt-Slice"></div>'
+    + '{{#show_arrow}}<div class="Ldt-Slice-Arrow"></div>{{/show_arrow}}'
+
 IriSP.Widgets.Slice.prototype.draw = function() {
     
-    this.$slider = IriSP.jQuery('<div>')
-        .addClass("Ldt-Slice")
+    this.renderTemplate();
+    
+    this.$slider = this.$.find(".Ldt-Slice");
     
-    this.$.append(this.$slider);
+    if (this.show_arrow) {
+        this.insertSubwidget(this.$.find(".Ldt-Slice-Arrow"), { type: "Arrow" },"arrow");
+    }
     
     this.min = 0;
-    this.max = this.source.getDuration().valueOf();
+    this.max = this.media.duration.valueOf();
     
     var _this = this,
         _currentTime;
     
     this.$slider.slider({
         range: true,
-        values: [0, 0],
+        values: [0, this.max],
         min: 0,
         max: this.max,
         change: function(event, ui) {
-            _this.player.popcorn.trigger("IriSP.Arrow.updatePosition",{
-                widget:_this.type,
-                time:Math.floor((ui.values[0]+ui.values[1])/2)
-            });
-            _this.player.popcorn.trigger("IriSP.Slice.boundsChanged",[ui.values[0], ui.values[1]]);
+            if (_this.arrow) {
+                _this.arrow.moveToTime((ui.values[0]+ui.values[1])/2)
+            }
+            if (_this.onBoundsChanged) {
+                _this.onBoundsChanged(ui.values[0],ui.values[1]);
+            }
         },
         start: function() {
             _this.sliding = true;
-            if (!_this.player.popcorn.media.paused) {
-                _this.player.popcorn.pause();
+            if (!_this.media.getPaused) {
+                _this.media.pause();
             }
-            _currentTime = _this.player.popcorn.currentTime();
+            _currentTime = _this.media.getCurrentTime();
         },
         slide: function(event, ui) {
-            if (!_this.override_bounds && (ui.value < _this.min || ui.value > _this.max)) {
-                return false;
-            }
-            _this.player.popcorn.currentTime(ui.value / 1000);
+            _this.media.setCurrentTime(ui.value);
         },
         stop: function() {
             _this.sliding = false;
-            _this.player.popcorn.currentTime(_currentTime);
+            _this.media.setCurrentTime(_currentTime);
         }
     });
+    
     this.$slider.find(".ui-slider-handle:first").addClass("Ldt-Slice-left-handle");
     this.$slider.find(".ui-slider-handle:last").addClass("Ldt-Slice-right-handle");
-    if (this.start_visible) {
-        this.show();
-    } else {
-        this.hide();
-    }
-    this.bindPopcorn("IriSP.Slice.show","show");
-    this.bindPopcorn("IriSP.Slice.hide","hide");
-    this.bindPopcorn("IriSP.Annotation.boundsChanged","storeBounds");
-    this.player.popcorn.trigger("IriSP.Annotation.getBounds");
+    
+    this.getWidgetAnnotations().forEach(function(_a) {
+        _a.on("enter", function() {
+            _this.$slider.slider("values",[_a.begin, _a.end]);
+        });
+    });
 };
 
 IriSP.Widgets.Slice.prototype.show = function() {
     this.$slider.show();
-    this.player.popcorn.trigger("IriSP.Arrow.takeover",this.type);
-    this.$slider.slider("values", [this.min, this.max]);
 }
 
 IriSP.Widgets.Slice.prototype.hide = function() {
     this.$slider.hide();
-    this.player.popcorn.trigger("IriSP.Arrow.release");
 }
-
-IriSP.Widgets.Slice.prototype.storeBounds = function(_values) {
-    if (!this.player.popcorn.media.paused && (this.min != _values[0] || this.max != _values[1])) {
-        this.min = _values[0];
-        this.max = _values[1];
-        if (this.live_update && !this.sliding) {
-            this.$slider.slider("values", [this.min, this.max]);
-        }
-    }
-}
\ No newline at end of file
--- a/metadataplayer/metadataplayer/Slider.css	Fri Aug 31 17:43:31 2012 +0200
+++ b/metadataplayer/metadataplayer/Slider.css	Thu Sep 27 12:23:41 2012 +0200
@@ -12,3 +12,8 @@
     background: #747474;
 }
 
+.Ldt-Slider-Time {
+    position: absolute; top: -16px; background: #ffffc0; color: #000000; border-radius: 3px; z-index: 8;
+    font-size: 9px; width: 34px; border: 1px solid #999999; padding: 1px; margin-left: -20px;
+    display: none; text-align: center; font-weight: bold;
+}
--- a/metadataplayer/metadataplayer/Slider.js	Fri Aug 31 17:43:31 2012 +0200
+++ b/metadataplayer/metadataplayer/Slider.js	Thu Sep 27 12:23:41 2012 +0200
@@ -15,12 +15,16 @@
                                 set to zero for fixed slider */
 };
 
+IriSP.Widgets.Slider.prototype.template =
+    '<div class="Ldt-Slider"></div><div class="Ldt-Slider-Time">00:00</div>'
+
 IriSP.Widgets.Slider.prototype.draw = function() {
     
-    this.$slider = IriSP.jQuery('<div>')
-        .addClass("Ldt-Slider");
+    this.renderTemplate();
     
-    this.$.append(this.$slider);
+    this.$time = this.$.find(".Ldt-Slider-Time");
+    
+    this.$slider = this.$.find(".Ldt-Slider");
     
     var _this = this;
     
@@ -30,34 +34,41 @@
         min: 0,
         max: this.source.getDuration().milliseconds,
         slide: function(event, ui) {
-            _this.player.popcorn.currentTime(ui.value / 1000);
-            _this.player.popcorn.trigger("IriSP.Mediafragment.setHashToTime");
+            _this.media.setCurrentTime(ui.value);
+            _this.player.trigger("Mediafragment.setHashToTime");
         }
     });
     
     this.$handle = this.$slider.find('.ui-slider-handle');
     
-    this.bindPopcorn("timeupdate","onTimeupdate");
-    this.bindPopcorn("IriSP.PlayerWidget.MouseOver","onMouseover");
-    this.bindPopcorn("IriSP.PlayerWidget.MouseOut","onMouseout");
+    this.onMediaEvent("timeupdate","onTimeupdate");
+    this.onMdpEvent("Player.MouseOver","onMouseover");
+    this.onMdpEvent("Player.MouseOut","onMouseout");
     
     if (this.minimize_timeout) {
         this.$slider.css(this.calculateSliderCss(this.minimized_height));
         this.$handle.css(this.calculateHandleCss(this.minimized_height));
         
-        this.$
-            .mouseover(this.functionWrapper("onMouseover"))
-            .mouseout(this.functionWrapper("onMouseout"));
-        
         this.maximized = false;
         this.timeoutId = false;
     }
+    
+    this.$
+        .mouseover(function() {
+            _this.$time.show();
+            _this.onMouseover();
+        })
+        .mouseout(this.functionWrapper("onMouseout"))
+        .mousemove(function(_e) {
+            var _x = _e.pageX - _this.$.offset().left,
+                _t = new IriSP.Model.Time(_this.media.duration * _x / _this.width);
+            _this.$time.text(_t.toString()).css("left",_x);
+        });
 };
 
-IriSP.Widgets.Slider.prototype.onTimeupdate = function() {
-    var _time = 1000 * this.player.popcorn.currentTime();
+IriSP.Widgets.Slider.prototype.onTimeupdate = function(_time) {
     this.$slider.slider("value",_time);
-    this.player.popcorn.trigger("IriSP.Arrow.updatePosition",{widget: this.type, time: _time});
+    this.player.trigger("Arrow.updatePosition",{widget: this.type, time: _time});
 }
 
 IriSP.Widgets.Slider.prototype.onMouseover = function() {
@@ -74,6 +85,7 @@
 }
 
 IriSP.Widgets.Slider.prototype.onMouseout = function() {
+    this.$time.hide();
     if (this.minimize_timeout) {
         if (this.timeoutId) {
             window.clearTimeout(this.timeoutId);
--- a/metadataplayer/metadataplayer/Slideshare.js	Fri Aug 31 17:43:31 2012 +0200
+++ b/metadataplayer/metadataplayer/Slideshare.js	Thu Sep 27 12:23:41 2012 +0200
@@ -2,12 +2,6 @@
 
 IriSP.Widgets.Slideshare = function(player, config) {
     IriSP.Widgets.Widget.call(this, player, config);
-    this.lastSlide = {
-        presentation: "",
-        slide: 0
-    }
-    this.embedObject = null;
-    this.oembedCache = {}
 }
 
 IriSP.Widgets.Slideshare.prototype = new IriSP.Widgets.Widget();
@@ -32,99 +26,94 @@
     '<div class="Ldt-SlideShare"><h2>{{l10n.slides_}}</h2><hr /><div class="Ldt-SlideShare-Container"></div></div>';
 
 IriSP.Widgets.Slideshare.prototype.draw = function() {
-    var _hide = false;
-    if (typeof this.annotation_type !== "undefined" && this.annotation_type) {
-        var _annType = this.source.getAnnotationTypes().searchByTitle(this.annotation_type);
-        _hide = !_annType.length;
+    
+    var _embedObject = null,
+        $container,
+        _lastEmbedded = "",
+        _this = this;
+    
+    function insertSlideshare(_presentation, _slide) {
+        if (_lastEmbedded === _presentation) {
+            if (_.embedObject && typeof _embedObject.jumpTo === "function") {
+                _embedObject.jumpTo(parseInt(_slide));
+            }
+        } else {
+            _lastEmbedded = _presentation;
+            var _id = IriSP.Model.getUID(),
+                _params = {
+                    allowScriptAccess: "always"
+                }
+                _atts = {
+                    id: _id
+                },
+                _flashvars = {
+                    doc : _presentation,
+                    startSlide : _slide
+                };
+            $container.html('<div id="' + _id + '"></div>');
+            swfobject.embedSWF(
+                "http://static.slidesharecdn.com/swf/ssplayer2.swf",
+                _id,
+                _this.embed_width,
+                _this.embed_height,
+                "8",
+                null,
+                _flashvars,
+                _params,
+                _atts
+            );
+            _embedObject = document.getElementById(_id);
+        }
+        $container.show();
     }
-    if (_hide) {
+    
+    var _annotations = this.getWidgetAnnotations();
+    if (!_annotations.length) {
         this.$.hide();
     } else {
         this.renderTemplate();
-        this.$container = this.$.find(".Ldt-SlideShare-Container");
-        this.bindPopcorn("timeupdate","onTimeupdate");
-        this.onTimeupdate();
+        var _lastPres = "",
+            _embedObject = null,
+            _oembedCache = {},
+            _this = this;
+        $container = this.$.find(".Ldt-SlideShare-Container");
+        _annotations.forEach(function(_a) {
+            _a.on("leave", function() {
+                $container.hide();
+                _lastPres = "";
+            });
+            _a.on("enter", function() {
+                var _description = _a.description,
+                    _isurl = /^https?:\/\//.test(_description),
+                    _presentation = _description.replace(/#.*$/,''),
+                    _slidematch = _description.match(/(#|\?|&)id=(\d+)/),
+                    _slide = parseInt(_slidematch && _slidematch.length > 2 ? _slidematch[2] : 1);
+                if (_presentation !== _lastPres) {
+                    if (_isurl) {
+                        if (typeof _oembedCache[_presentation] === "undefined") {
+                            var _ajaxUrl = "http://www.slideshare.net/api/oembed/1?url="
+                                + encodeURIComponent(_presentation)
+                                + "&format=jsonp&callback=?";
+                            IriSP.jQuery.getJSON(_ajaxUrl, function(_oembedData) {
+                                var _presmatch = _oembedData.html.match(/doc=([a-z0-9\-_%]+)/i);
+                                if (_presmatch && _presmatch.length > 1) {
+                                    _oembedCache[_presentation] =  _presmatch[1];
+                                    insertSlideshare(_presmatch[1], _slide);
+                                }
+                            });
+                        } else {
+                            insertSlideshare(_oembedCache[_presentation], _slide);
+                        }
+                    } else {
+                        insertSlideshare(_presentation, _slide);
+                    }
+                }
+                if (_this.sync && _embedObject && typeof _embedObject.jumpTo === "function") {
+                    _embedObject.jumpTo(parseInt(_slide));
+                }
+                _lastPres = _presentation;
+                
+            })
+        })
     }
 }
-
-IriSP.Widgets.Slideshare.prototype.onTimeupdate = function() {
-    var _list = this.getWidgetAnnotationsAtTime();
-    if (_list.length) {
-        var _description = _list[0].description,
-            _isurl = /^https?:\/\//.test(_description),
-            _presentation = _description.replace(/#.*$/,''),
-            _slidematch = _description.match(/(#|\?|&)id=(\d+)/),
-            _slide = parseInt(_slidematch && _slidematch.length > 2 ? _slidematch[2] : 1),
-            _this = this;
-        if (_presentation !== this.lastSlide.presentation) {
-            if (_isurl) {
-                if (typeof this.oembedCache[_presentation] === "undefined") {
-                    var _ajaxUrl = "http://www.slideshare.net/api/oembed/1?url="
-                        + encodeURIComponent(_presentation)
-                        + "&format=jsonp&callback=?";
-                    IriSP.jQuery.getJSON(_ajaxUrl, function(_oembedData) {
-                        var _presmatch = _oembedData.html.match(/doc=([a-z0-9\-_%]+)/i);
-                        if (_presmatch && _presmatch.length > 1) {
-                            _this.oembedCache[_presentation] =  _presmatch[1];
-                            _this.insertSlideshare(_presmatch[1], _slide);
-                        }
-                    });
-                } else {
-                    this.insertSlideshare(this.oembedCache[_presentation], _slide);
-                }
-            } else {
-                this.insertSlideshare(_presentation, _slide);
-            }
-        }
-        if (_slide != this.lastSlide.slide && this.sync && this.embedObject && typeof this.embedObject.jumpTo === "function") {
-            this.embedObject.jumpTo(parseInt(_slide));
-        }
-        this.lastSlide = {
-            presentation: _presentation,
-            slide: _slide
-        }
-    } else {
-        if (this.lastSlide.presentation) {
-            this.$container.hide();
-            this.lastSlide = {
-                presentation: "",
-                slide: 0
-            }
-        }
-    }
-}
-
-IriSP.Widgets.Slideshare.prototype.insertSlideshare = function(_presentation, _slide) {
-    if (this.lastEmbedded === _presentation) {
-        if (this.embedObject && typeof this.embedObject.jumpTo === "function") {
-            this.embedObject.jumpTo(parseInt(_slide));
-        }
-    } else {
-        this.lastEmbedded = _presentation;
-        var _id = IriSP.Model.getUID(),
-            _params = {
-                allowScriptAccess: "always"
-            }
-            _atts = {
-                id: _id
-            },
-            _flashvars = {
-                doc : _presentation,
-                startSlide : _slide
-            };
-        this.$container.html('<div id="' + _id + '"></div>');
-        swfobject.embedSWF(
-            "http://static.slidesharecdn.com/swf/ssplayer2.swf",
-            _id,
-            this.embed_width,
-            this.embed_height,
-            "8",
-            null,
-            _flashvars,
-            _params,
-            _atts
-        );
-        this.embedObject = document.getElementById(_id);
-    }
-    this.$container.show();
-}
--- a/metadataplayer/metadataplayer/Sparkline.js	Fri Aug 31 17:43:31 2012 +0200
+++ b/metadataplayer/metadataplayer/Sparkline.js	Thu Sep 27 12:23:41 2012 +0200
@@ -1,6 +1,5 @@
 IriSP.Widgets.Sparkline = function(player, config) {
     IriSP.Widgets.Widget.call(this, player, config);
-    //this.bindPopcorn("timeupdate", "onTimeupdate");
 };
 
 IriSP.Widgets.Sparkline.prototype = new IriSP.Widgets.Widget();
@@ -68,14 +67,14 @@
    
     this.$.click(function(_e) {
         var _x = _e.pageX - _this.$.offset().left;
-        _this.player.popcorn.currentTime(_this.source.getDuration().getSeconds() * _x / _this.width);
+        _this.media.setCurrentTime(_this.media.duration * _x / _this.width);
     });
     
-    this.bindPopcorn("timeupdate","onTimeupdate");
+    this.onMediaEvent("timeupdate","onTimeupdate");
 }
 
-IriSP.Widgets.Sparkline.prototype.onTimeupdate = function() {
-    var _x = Math.floor( this.width * this.player.popcorn.currentTime() / this.source.getDuration().getSeconds());
+IriSP.Widgets.Sparkline.prototype.onTimeupdate = function(_time) {
+    var _x = Math.floor( this.width * _time / this.media.duration);
     this.rectangleProgress.attr({
         "width" : _x
     });
--- a/metadataplayer/metadataplayer/Tagcloud.js	Fri Aug 31 17:43:31 2012 +0200
+++ b/metadataplayer/metadataplayer/Tagcloud.js	Thu Sep 27 12:23:41 2012 +0200
@@ -39,41 +39,32 @@
 }
 
 IriSP.Widgets.Tagcloud.prototype.draw = function() {
-    this.bindPopcorn("IriSP.search", "onSearch");
-    this.bindPopcorn("IriSP.search.closed", "onSearch");
-    this.bindPopcorn("IriSP.search.cleared", "onSearch");
+    this.onMdpEvent("search", "onSearch");
+    this.onMdpEvent("search.closed", "onSearch");
+    this.onMdpEvent("search.cleared", "onSearch");
     
     if (this.segment_annotation_type) {
-        this.bindPopcorn("timeupdate","onTimeupdate");
+        var _this = this;
+        this.source.getAnnotationsByTypeTitle(this.segment_annotation_type).forEach(function(_a) {
+            _a.on("enter", function() {
+                _this.redraw(_a.begin, _a.end);
+            })
+        });
     } else {
         this.redraw();
     }
 }
 
-IriSP.Widgets.Tagcloud.prototype.onTimeupdate = function() {
-    var _time = Math.floor(this.player.popcorn.currentTime() * 1000),
-        _list = this.source.getAnnotationsByTypeTitle(this.segment_annotation_type).filter(function(_annotation) {
-            return _annotation.begin <= _time && _annotation.end > _time;
-        });
-    if (_list.length) {
-        if (_list[0].begin !== this.begin_time || _list[0].end !== this.end_time) {
-            this.begin_time = _list[0].begin;
-            this.end_time = _list[0].end;
-            this.redraw();
-        }
-    }
-}
-
-IriSP.Widgets.Tagcloud.prototype.redraw = function() {
+IriSP.Widgets.Tagcloud.prototype.redraw = function(_from, _to) {
     var _urlRegExp = /https?:\/\/[0-9a-zA-Z\.%\/-_]+/g,
         _regexpword = /[^\s\.&;,'"!\?\d\(\)\+\[\]\\\…\-«»:\/]{3,}/g,
         _words = {},
         _this = this,
         _annotations = this.getWidgetAnnotations();
         
-    if (typeof this.begin_time !== "undefined" && typeof this.end_time !== "undefined") {
+    if (typeof _from !== "undefined" && typeof _to !== "undefined") {
         _annotations = _annotations.filter(function(_annotation) {
-            return _annotation.begin >= _this.begin_time && _annotation.end <= _this.end_time;
+            return _annotation.begin >= _from && _annotation.end <= _to;
         })
     }
     
@@ -117,7 +108,7 @@
     this.$.html(Mustache.to_html(this.template,  {words: _words }));
     this.$.find(".Ldt-Tagcloud-item").click(function() {
         var _txt = IriSP.jQuery(this).attr("content");
-        _this.player.popcorn.trigger("IriSP.search.triggeredSearch", _txt);
+        _this.player.trigger("search.triggeredSearch", _txt);
     });
     
 }
--- a/metadataplayer/metadataplayer/Tagger.css	Fri Aug 31 17:43:31 2012 +0200
+++ b/metadataplayer/metadataplayer/Tagger.css	Thu Sep 27 12:23:41 2012 +0200
@@ -1,3 +1,14 @@
+
 .Ldt-Tagger {
-    border: 2px solid #666; margin: 2px 0; padding: 5px;
+    border-style: solid;
+    border-width: 1px;
+    border-color: #b7b7b7;
+    padding: 1px;
+    margin: 0;
 }
+
+.Ldt-Tagger-Inner {
+    background: url(img/pinstripe.png);
+    padding: 5px;
+    margin: 0;
+}
--- a/metadataplayer/metadataplayer/Tagger.js	Fri Aug 31 17:43:31 2012 +0200
+++ b/metadataplayer/metadataplayer/Tagger.js	Thu Sep 27 12:23:41 2012 +0200
@@ -26,8 +26,8 @@
 }
 
 IriSP.Widgets.Tagger.prototype.template =
-    '<form class="Ldt-Tagger"><input class="Ldt-Tagger-Input" placeholder="{{l10n.add_a_tag}}" />'
-    + '<input class="Ldt-Tagger-Submit" type="submit" value="{{l10n.submit}}" /></form>';
+    '<div class="Ldt-Tagger"><form class="Ldt-Tagger-Inner"><input class="Ldt-Tagger-Input" placeholder="{{l10n.add_a_tag}}" />'
+    + '<input class="Ldt-Tagger-Submit" type="submit" value="{{l10n.submit}}" /></form></div>';
 
 IriSP.Widgets.Tagger.prototype.draw = function() {
     this.renderTemplate();
@@ -39,7 +39,7 @@
     });
     if (this.pause_on_write) {
         _input.keyup(function() {
-            _this.player.popcorn.pause();
+            _this.media.pause();
         });
     }
     this.$.find(".Ldt-Tagger").submit(function() {
@@ -72,7 +72,7 @@
              * Nous remplissons les données de l'annotation générée à la volée
              * ATTENTION: Si nous sommes sur un MASHUP, ces éléments doivent se référer AU MEDIA D'ORIGINE
              * */
-            var _now = 1000*_this.player.popcorn.currentTime(),
+            var _now = _this.media.getCurrentTime(),
                 _pilotAnnotation = null;
             if (_this.source.currentMedia.elementType == "mashup") {
                 /* Si c'est un mashup, on récupère l'annotation d'origine pour caler le temps */
@@ -126,11 +126,11 @@
                     _export.deSerialize(_data);
                     /* On récupère les données réimportées dans l'espace global des données */
                     _this.source.merge(_export);
-                    if (_this.pause_on_write && _this.player.popcorn.media.paused) {
-                        _this.player.popcorn.play();
+                    if (_this.pause_on_write && _this.media.getPaused()) {
+                        _this.media.play();
                     }
                     /* On force le rafraîchissement du widget AnnotationsList */
-                    _this.player.popcorn.trigger("IriSP.AnnotationsList.refresh");
+                    _this.player.trigger("AnnotationsList.refresh");
                 },
                 error: function(_xhr, _error, _thrown) {
                     console.log("Error when sending annotation", _thrown);
--- a/metadataplayer/metadataplayer/Tooltip.js	Fri Aug 31 17:43:31 2012 +0200
+++ b/metadataplayer/metadataplayer/Tooltip.js	Thu Sep 27 12:23:41 2012 +0200
@@ -1,6 +1,6 @@
 /* this widget displays a small tooltip */
-IriSP.Widgets.Tooltip = function(Popcorn, config, Serializer) {
-    IriSP.Widgets.Widget.call(this, Popcorn, config, Serializer);
+IriSP.Widgets.Tooltip = function(player, config) {
+    IriSP.Widgets.Widget.call(this, player, config);
 };
 
 IriSP.Widgets.Tooltip.prototype = new IriSP.Widgets.Widget();
--- a/metadataplayer/metadataplayer/Trace.js	Fri Aug 31 17:43:31 2012 +0200
+++ b/metadataplayer/metadataplayer/Trace.js	Thu Sep 27 12:23:41 2012 +0200
@@ -22,15 +22,17 @@
       return;
   }
   var _this = this,
-    _listeners = {
-        "IriSP.search.open" : 0,
-        "IriSP.search.closed" : 0,
-        "IriSP.search" : 0,
-        "IriSP.search.cleared" : 0,
-        "IriSP.search.matchFound" : 0,
-        "IriSP.search.noMatchFound" : 0,
-        "IriSP.search.triggeredSearch" : 0,
-        "IriSP.TraceWidget.MouseEvents" : 0,
+    _mdplisteners = {
+        "search.open" : 0,
+        "search.closed" : 0,
+        "search" : 0,
+        "search.cleared" : 0,
+        "search.matchFound" : 0,
+        "search.noMatchFound" : 0,
+        "search.triggeredSearch" : 0,
+        "TraceWidget.MouseEvents" : 0
+    }
+    _medialisteners = {
         "play" : 0,
         "pause" : 0,
         "volumechange" : 0,
@@ -39,14 +41,23 @@
         "pause" : 0,
         "timeupdate" : 2000
     };
-    IriSP._(_listeners).each(function(_ms, _listener) {
+    IriSP._(_mdplisteners).each(function(_ms, _listener) {
         var _f = function(_arg) {
             _this.eventHandler(_listener, _arg);
         }
         if (_ms) {
             _f = IriSP._.throttle(_f, _ms);
         }
-        _this.player.popcorn.listen(_listener, _f);
+        _this.onMdpEvent(_listener, _f);
+    });
+    IriSP._(_medialisteners).each(function(_ms, _listener) {
+        var _f = function(_arg) {
+            _this.eventHandler(_listener, _arg);
+        }
+        if (_ms) {
+            _f = IriSP._.throttle(_f, _ms);
+        }
+        _this.media.on(_listener, _f);
     });
     
     if (!this.tracer) {
@@ -63,12 +74,8 @@
     this.tracer.trace("TraceWidgetInit", {});
     
     this.mouseLocation = '';
-    IriSP.jQuery(".Ldt-Widget").bind("click mouseover mouseout", function(_e) {
-        var _target = IriSP.jQuery(_e.target);
-
-        while (!_target.hasClass("Ldt-TraceMe") && !_target.hasClass("Ldt-Widget") && _target.length) {
-            _target = _target.parent();
-        }
+    IriSP.jQuery(".Ldt-Widget").on("click mouseover mouseout", ".Ldt-TraceMe", function(_e) {
+        var _target = IriSP.jQuery(this);
         
         var _widget = IriSP.jQuery(this).attr("widget-type"),
             _data = {
@@ -95,7 +102,7 @@
         switch(_e.type) {
             case "mouseover":
                 if (_this.lastTarget != _lastTarget) {
-                    _this.player.popcorn.trigger('IriSP.TraceWidget.MouseEvents', _data);
+                    _this.player.trigger('TraceWidget.MouseEvents', _data);
                 } else {
                     if (typeof _this.moTimeout != "undefined") {
                         clearTimeout(_this.moTimeout);
@@ -109,12 +116,12 @@
                 }
                 _this.moTimeout = setTimeout(function() {
                    if (_lastTarget != _this.lastTarget) {
-                       _this.player.popcorn.trigger('IriSP.TraceWidget.MouseEvents', _data);
+                       _this.player.trigger('TraceWidget.MouseEvents', _data);
                    }
                 },100);
             break;
             default:
-                _this.player.popcorn.trigger('IriSP.TraceWidget.MouseEvents', _data);
+                _this.player.trigger('TraceWidget.MouseEvents', _data);
         }
         _this.lastTarget = _lastTarget;
     });
@@ -134,16 +141,16 @@
             delete _arg.widget;
             delete _arg.type;
         break;
-        case 'timeupdate':
         case 'play':
         case 'pause':
-            _arg.time = this.player.popcorn.currentTime() * 1000;
+            _arg.milliseconds = this.media.getCurrentTime().milliseconds;
+        case 'timeupdate':
         case 'seeked':
         case 'volumechange':
-            _traceName += 'Popcorn_' + _listener;
+            _traceName += 'media_' + _listener;
         break;
         default:
-            _traceName += _listener.replace('IriSP.','').replace('.','_');
+            _traceName += _listener.replace('.','_');
     }
     this.lastEvent = _traceName;
     if (typeof this.extend === "object" && this.extend) {
--- a/metadataplayer/metadataplayer/Tweet.js	Fri Aug 31 17:43:31 2012 +0200
+++ b/metadataplayer/metadataplayer/Tweet.js	Thu Sep 27 12:23:41 2012 +0200
@@ -25,6 +25,7 @@
             "color" : "#05aae6"
         }
     ],
+    annotation_type: "tweet",
     pin_at_start: false
 }
 
@@ -66,7 +67,6 @@
 
 IriSP.Widgets.Tweet.prototype.draw = function() {
     this.renderTemplate();
-    this.bindPopcorn("IriSP.Tweet.show","show");
     this.pinned = this.pin_at_start;
     var _this = this;
     this.$.find(".Ldt-Tweet-Pin").click(function() {
@@ -84,10 +84,14 @@
         _this.hide();
     });
     this.$.hide();
+    this.getWidgetAnnotations().forEach(function(_annotation) {
+        _annotation.on("click", function() {
+            _this.show(_annotation);
+        });
+    });
 }
 
-IriSP.Widgets.Tweet.prototype.show = function(_id) {
-    var _tweet = this.source.getElement(_id);
+IriSP.Widgets.Tweet.prototype.show = function(_tweet) {
     if (typeof _tweet !== "undefined" && typeof _tweet.source !== "undefined") {
         var _entities = [];
         for (var _i = 0; _i < _tweet.source.entities.hashtags.length; _i++) {
@@ -170,7 +174,7 @@
         this.$.find(".Ldt-Tweet-Retweet").attr("href", "https://twitter.com/intent/retweet?tweet_id=" + _tweet.source.id_str);
         this.$.find(".Ldt-Tweet-Reply").attr("href", "https://twitter.com/intent/tweet?in_reply_to=" + _tweet.source.id_str);
         this.$.find(".Ldt-Tweet-Original").attr("href", "https://twitter.com/" + _tweet.source.user.screen_name + "/status/" + _tweet.source.id_str);
-        this.player.popcorn.trigger("IriSP.Annotation.minimize");
+        this.player.trigger("Annotation.minimize");
         this.$.slideDown();
         this.cancelTimeout();
         if (!this.pinned) {
@@ -182,7 +186,7 @@
 }
 
 IriSP.Widgets.Tweet.prototype.hide = function() {
-    this.player.popcorn.trigger("IriSP.Annotation.maximize");
+    this.player.trigger("Annotation.maximize");
     this.$.slideUp();
     this.cancelTimeout();
 }
Binary file metadataplayer/metadataplayer/img/pinstripe-grey.png has changed
Binary file metadataplayer/metadataplayer/img/voiceannotation.png has changed
Binary file metadataplayer/metadataplayer/img/zoombuttons.png has changed
--- a/metadataplayer/oggvideo.htm	Fri Aug 31 17:43:31 2012 +0200
+++ b/metadataplayer/oggvideo.htm	Thu Sep 27 12:23:41 2012 +0200
@@ -7,15 +7,44 @@
         <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 / 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">
-            testConfig('json/ldt-ogv.json', true);
+IriSP.libFiles.defaultDir = "libs/";
+IriSP.widgetsDir = "metadataplayer";
+var _metadata = {
+    url: "json/ldt-ogv.json",
+    format: 'ldt'
+};
+var _config = {
+    width : 620,
+    container : 'LdtPlayer',
+    default_options: {
+        metadata: _metadata
+    },
+    css : 'metadataplayer/LdtPlayer-core.css',
+    widgets: [
+        { type: "AutoPlayer" },
+        { type: "Sparkline" },
+        { type: "Slider" },
+        { type: "Controller" },
+        { type: "Polemic" },
+        { type: "Segments" },
+        { type: "Annotation" },
+        { type: "Tweet" },
+        { type: "Tagcloud" },
+        {
+            type: "AnnotationsList",
+            container: "AnnotationsListContainer"
+        },
+        { type: "Mediafragment"}
+    ]
+};
+_myPlayer = new IriSP.Metadataplayer(_config);
         </script>
     </body>
 </html>
--- a/metadataplayer/test-config.js	Fri Aug 31 17:43:31 2012 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,54 +0,0 @@
-function testConfig(_urlMetadata, _useLocalBuild) {
-    document.getElementById('LdtPlayer').innerHTML = '';
-    _useLocalBuild = (typeof _useLocalBuild !== "undefined" && _useLocalBuild)
-    IriSP.libFiles.defaultDir = _useLocalBuild ? "libs/" : "../src/js/libs/";
-    IriSP.widgetsDir = _useLocalBuild ? "metadataplayer" : "../src/widgets";
-    var _metadata = {
-        url: _urlMetadata,
-        format: 'ldt'
-    };
-    var _config = {            
-        gui: {
-            width : 620,
-            container : 'LdtPlayer',
-            default_options: {
-                metadata: _metadata
-            },
-            css : _useLocalBuild ? 'metadataplayer/LdtPlayer-core.css' : '../src/css/LdtPlayer-core.css',
-            widgets: [
-                { type: "Sparkline" },
-                { type: "Slider" },
-                { type: "Controller" },
-                { type: "Polemic" },
-                { type: "Segments" },
-                { type: "Slice" },
-                { type: "Arrow" },
-                { type: "Annotation" },
-                { type: "CreateAnnotation" },
-                { type: "Tweet" },
-                { type: "Tagcloud" },
-                {
-                    type: "AnnotationsList",
-                    container: "AnnotationsListContainer"
-                },
-                { type: "Mediafragment"},
-                {
-                    type: "Trace",
-                    default_subject: "tests-iri",
-                    js_console: true
-                }
-            ]
-        },
-        player:{
-            type:'auto',
-            live: true, 
-            height: 350, 
-            width: 620, 
-            provider: "rtmp",
-            autostart: true,
-            metadata: _metadata
-        }
-    };
-    
-    return new IriSP.Metadataplayer(_config);
-}
\ No newline at end of file
--- a/metadataplayer/youtube.htm	Fri Aug 31 17:43:31 2012 +0200
+++ b/metadataplayer/youtube.htm	Thu Sep 27 12:23:41 2012 +0200
@@ -7,7 +7,6 @@
         <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="../build/LdtPlayer-core.js" type="text/javascript"></script>
-        <script type="text/javascript" src="test-config.js" type="text/javascript"></script>
     </head>
 
     <body>
@@ -15,7 +14,38 @@
         <div id="LdtPlayer"></div>
         <div id="AnnotationsListContainer"></div>
         <script type="text/javascript">
-            testConfig('json/ldt-youtube.json', true);
+IriSP.libFiles.defaultDir = "libs/";
+IriSP.widgetsDir = "metadataplayer";
+var _metadata = {
+    url: "json/ldt-youtube.json",
+    format: 'ldt'
+};
+var _config = {
+    width : 620,
+    container : 'LdtPlayer',
+    default_options: {
+        metadata: _metadata
+    },
+    css : 'metadataplayer/LdtPlayer-core.css',
+    widgets: [
+        { type: "AutoPlayer" },
+        { type: "Sparkline" },
+        { type: "Slider" },
+        { type: "Controller" },
+        { type: "Polemic" },
+        { type: "Segments" },
+        { type: "Annotation" },
+        { type: "CreateAnnotation" },
+        { type: "Tweet" },
+        { type: "Tagcloud" },
+        {
+            type: "AnnotationsList",
+            container: "AnnotationsListContainer"
+        },
+        { type: "Mediafragment"}
+    ]
+};
+_myPlayer = new IriSP.Metadataplayer(_config);
         </script>
     </body>
 </html>