--- 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 & 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">×</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…')
+ }))
+ 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">×</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…')
+ }));
+ 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,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/")};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,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/")};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 :</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 !</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;">© 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 :</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 !</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;">© 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 :</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 !</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;">© 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…'),
- 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…'),
+ 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>