";
+IriSP.tweetWidget_template = "{{! template for the tweet widget }}";/* utils.js - various utils that don't belong anywhere else */
+
+/* trace function, for debugging */
+
+IriSP.traceNum = 0;
+IriSP.trace = function( msg, value ) {
+/*
+ if( IriSP.config.gui.debug === true ) {
+ IriSP.traceNum += 1;
+ IriSP.jQuery( "
"+IriSP.traceNum+" - "+msg+" : "+value+"
" ).appendTo( "#Ldt-output" );
+ }
+*/
+};
+
+/* used in callbacks - because in callbacks we lose "this",
+ we need to have a special function which wraps "this" in
+ a closure. This way, the
+*/
+IriSP.wrap = function (obj, fn) {
+ return function() {
+ var args = Array.prototype.slice.call(arguments, 0);
+ return fn.apply(obj, args);
+ }
+}
+
+/* convert a time to a percentage in the media */
+IriSP.timeToPourcent = function(time, timetotal){
+ var time = Math.abs(time);
+ var timetotal = Math.abs(timetotal);
+
+ return Math.floor((time/timetotal) * 100);
+};
+
+IriSP.padWithZeros = function(num) {
+ if (Math.abs(num) < 10) {
+ return "0" + num.toString();
+ } else {
+ return num.toString();
+ }
+};
+
+/* convert a number of milliseconds to a tuple of the form
+ [hours, minutes, seconds]
+*/
+IriSP.msToTime = function(ms) {
+ return IriSP.secondsToTime(ms / 1000);
+}
+/* convert a number of seconds to a tuple of the form
+ [hours, minutes, seconds]
+*/
+IriSP.secondsToTime = function(secs) {
+ var hours = Math.abs(parseInt( secs / 3600 ) % 24);
+ var minutes = Math.abs(parseInt( secs / 60 ) % 60);
+ var seconds = parseFloat(Math.abs(secs % 60).toFixed(0));
+
+ var toString_fn = function() {
+ var ret = "";
+ if (hours > 0)
+ ret = IriSP.padWithZeros(this.hours) + ":";
+ ret += IriSP.padWithZeros(this.minutes) + ":" + IriSP.padWithZeros(this.seconds);
+
+ return ret;
+ }
+ return {"hours" : hours, "minutes" : minutes, "seconds" : seconds, toString: toString_fn};
+};
+
+/* format a tweet - replaces @name by a link to the profile, #hashtag, etc. */
+IriSP.formatTweet = function(tweet) {
+ /*
+ an array of arrays which hold a regexp and its replacement.
+ */
+ var regExps = [
+ /* copied from http://codegolf.stackexchange.com/questions/464/shortest-url-regex-match-in-javascript/480#480 */
+ [/((https?:\/\/)?[\w-]+(\.[\w-]+)+\.?(:\d+)?(\/\S*)?)/gi, "
$1"],
+ [/@(\w+)/gi, "
@$1"], // matches a @handle
+ [/#(\w+)/gi, "
#$1"],// matches a hashtag
+ [/(\+\+)/gi, "
$1"],
+ [/(--)/gi, "
$1"],
+ [/(==)/gi, "
$1"],
+ [/(\?\?)/gi, "
$1"]
+ ];
+
+ var i = 0;
+ for(i = 0; i < regExps.length; i++) {
+ tweet = tweet.replace(regExps[i][0], regExps[i][1]);
+ }
+
+ return tweet;
+};
+
+IriSP.countProperties = function(obj) {
+ var count = 0;
+
+ for(var prop in obj) {
+ if(obj.hasOwnProperty(prop))
+ ++count;
+ }
+
+ return count;
+};
+
+// conversion de couleur Decimal vers HexaDecimal || 000 si fff
+IriSP.DEC_HEXA_COLOR = function (dec) {
+ var val = +dec;
+ var str = val.toString(16);
+ var zeroes = "";
+ if (str.length < 6) {
+ for (var i = 0; i < 6 - str.length; i++)
+ zeroes += "0";
+ }
+ return zeroes + str;
+};
+
+/* shortcut to have global variables in templates */
+IriSP.templToHTML = function(template, values) {
+ var params = IriSP.jQuery.extend(IriSP.default_templates_vars, values);
+ return Mustache.to_html(template, params);
+};
+
+/* we need to be stricter than encodeURIComponent,
+ because of twitter
+*/
+IriSP.encodeURI = function(str) {
+ return encodeURIComponent(str).replace(/!/g, '%21').replace(/'/g, '%27').replace(/\(/g, '%28').
+ replace(/\)/g, '%29').replace(/\*/g, '%2A');
+}
+
+IriSP.__guidCounter = 0;
+IriSP.guid = function(prefix) {
+ IriSP.__guidCounter += 1;
+ return prefix + IriSP.__guidCounter;
+};
+
+/** returns an url to share on facebook */
+IriSP.mkFbUrl = function(url, text) {
+ if (typeof(text) === "undefined")
+ text = "I'm watching ";
+
+ return "http://www.facebook.com/share.php?u=" + IriSP.encodeURI(text) + IriSP.shorten_url(url);
+};
+
+/** returns an url to share on twitter */
+IriSP.mkTweetUrl = function(url, text) {
+ if (typeof(text) === "undefined")
+ text = "I'm watching ";
+
+ return "http://twitter.com/home?status=" + IriSP.encodeURI(text) + IriSP.shorten_url(url);
+};
+
+/** returns an url to share on google + */
+IriSP.mkGplusUrl = function(url, text) {
+ return "https://plusone.google.com/_/+1/confirm?hl=en&url=" + IriSP.shorten_url(url);
+};
+
+/** test if a value is null or undefined */
+IriSP.null_or_undefined = function(val) {
+ return (typeof(val) === "undefined" || val === null);
+};
+
+/** issue a call to an url shortener and return the shortened url */
+IriSP.shorten_url = function(url) {
+ if (IriSP.config.shortener.hasOwnProperty("shortening_function"))
+ return IriSP.config.shortener.shortening_function(url);
+
+ return url;
+};
+
+/** Similar to substr but remove the last word if
+ we're breaking a word in two.
+ */
+IriSP.clean_substr = function(str, start, end) {
+ var s = str.substr(start, end).substr(start, end).split(" ");
+ s.pop();
+ return s.join(" ");
+};
+/* for ie compatibility
+if (Object.prototype.__defineGetter__&&!Object.defineProperty) {
+ Object.defineProperty=function(obj,prop,desc) {
+ if ("get" in desc) obj.__defineGetter__(prop,desc.get);
+ if ("set" in desc) obj.__defineSetter__(prop,desc.set);
+ }
+}
+*/
+/* wrapper that simulates popcorn.js because
+ popcorn is a bit unstable at the time */
+
+IriSP.PopcornReplacement = {
+};
+
+/** base class for our popcorn-compatible players.
+ */
+IriSP.PopcornReplacement.player = function(container, options) {
+ /* the jwplayer calls the callbacks in the global space so we need to
+ preserve them using IriSP.wrap */
+ this.callbacks = {
+ onReady: IriSP.wrap(this, this.__initApi),
+ onTime: IriSP.wrap(this, this.__timeHandler),
+ onPlay: IriSP.wrap(this, this.__playHandler),
+ onPause: IriSP.wrap(this, this.__pauseHandler),
+ onSeek: IriSP.wrap(this, this.__seekHandler)
+ };
+
+ this.media = {
+ "paused": true,
+ "muted": false
+ };
+
+ this.container = container.slice(1); //eschew the '#'
+
+ this.msgPump = {}; /* dictionnary used to receive and send messages */
+ this.__codes = []; /* used to schedule the execution of a piece of code in
+ a segment (similar to the popcorn.code plugin). */
+
+ 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.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.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.jwplayer = function(container, options) {
+ IriSP.PopcornReplacement._container = container.slice(1); //eschew the '#'
+ options.events = {
+ onReady: IriSP.PopcornReplacement.__initApi,
+ onTime: IriSP.PopcornReplacement.__timeHandler,
+ onPlay: IriSP.PopcornReplacement.__playHandler,
+ onPause: IriSP.PopcornReplacement.__pauseHandler,
+ onSeek: IriSP.PopcornReplacement.__seekHandler
+ }
+
+ jwplayer(IriSP.PopcornReplacement._container).setup(options);
+ IriSP.PopcornReplacement.media.duration = options.duration;
+ return IriSP.PopcornReplacement;
+};
+*/
+
+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");
+ //IriSP.PopcornReplacement.trigger("playing");
+ this.playerFns.play();
+};
+
+IriSP.PopcornReplacement.player.prototype.pause = function() {
+ if ( !this.media.paused ) {
+ this.media.paused = true;
+ this.trigger( "pause" );
+ this.playerFns.pause();
+ }
+};
+
+IriSP.PopcornReplacement.player.prototype.muted = function(val) {
+ 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.mute = IriSP.PopcornReplacement.player.prototype.muted;
+
+IriSP.PopcornReplacement.player.prototype.code = function(options) {
+ this.__codes.push(options);
+ return this;
+};
+
+/* called everytime the player updates itself
+ (onTime event)
+ */
+
+IriSP.PopcornReplacement.player.prototype.__timeHandler = function(event) {
+ var pos = event.position;
+
+ var i = 0;
+ for(i = 0; i < this.__codes.length; i++) {
+ var c = this.__codes[i];
+
+ if (pos >= c.start && pos < c.end &&
+ pos - 1 <= c.start) {
+ c.onStart();
+ }
+
+ if (pos > c.start && pos > c.end &&
+ pos - 1 <= c.end) {
+ c.onEnd();
+ }
+
+ }
+
+ this.trigger("timeupdate");
+};
+
+IriSP.PopcornReplacement.player.prototype.__seekHandler = function(event) {
+ var i = 0;
+
+ for(i = 0; i < this.__codes.length; i++) {
+ var c = this.__codes[i];
+
+ if (event.position >= c.start && event.position < c.end) {
+ c.onEnd();
+ }
+ }
+
+ for(i = 0; i < this.__codes.length; i++) {
+ var c = this.__codes[i];
+
+ if (typeof(event.offset) === "undefined")
+ event.offset = 0;
+
+ if (event.offset >= c.start && event.offset < c.end) {
+ c.onStart();
+ }
+
+ }
+
+ /* this signal sends as an extra argument the position in the video.
+ As far as I know, this argument is not provided by popcorn */
+ this.trigger("seeked", event.offset);
+};
+
+IriSP.PopcornReplacement.player.prototype.__playHandler = function(event) {
+ this.media.paused = false;
+ this.trigger("play");
+};
+
+IriSP.PopcornReplacement.player.prototype.__pauseHandler = function(event) {
+ this.media.paused = true;
+ this.trigger("pause");
+};
+
+IriSP.PopcornReplacement.player.prototype.roundTime = function() {
+ var currentTime = this.currentTime();
+ return Math.round(currentTime);
+};/* data.js - this file deals with how the players gets and sends data */
+
+IriSP.DataLoader = function() {
+ this._cache = {};
+
+ /*
+ A structure to hold callbacks for specific urls. We need it because
+ ajax calls are asynchronous, so it means that sometimes we ask
+ multiple times for a ressource because the first call hasn't been
+ received yet.
+ */
+ this._callbacks = {};
+};
+
+IriSP.DataLoader.prototype.get = function(url, callback) {
+
+ var base_url = url.split("&")[0]
+ if (this._cache.hasOwnProperty(base_url)) {
+ callback(this._cache[base_url]);
+ } else {
+ if (!this._callbacks.hasOwnProperty(base_url)) {
+ this._callbacks[base_url] = [];
+ this._callbacks[base_url].push(callback);
+ /* we need a closure because this gets lost when it's called back */
+
+ // uncomment you don't want to use caching.
+ // IriSP.jQuery.get(url, callback);
+
+ var func = function(data) {
+ this._cache[base_url] = data;
+ var i = 0;
+
+ for (i = 0; i < this._callbacks[base_url].length; i++) {
+ this._callbacks[base_url][i](this._cache[base_url]);
+ }
+ };
+
+ /* automagically choose between json and jsonp */
+ if (url.indexOf(document.location.hostname) === -1 &&
+ url.indexOf("http://") !== -1 /* not a relative url */ ) {
+ // we contacting a foreign domain, use JSONP
+
+ IriSP.jQuery.get(url, {}, IriSP.wrap(this, func), "jsonp");
+ } else {
+
+ // otherwise, hey, whatever rows your boat
+ IriSP.jQuery.get(url, IriSP.wrap(this, func));
+ }
+
+ } else {
+ /* simply push the callback - it'll get called when the ressource
+ has been received */
+
+ this._callbacks[base_url].push(callback);
+
+ }
+ }
+}
+
+/* the base abstract "class" */
+IriSP.Serializer = function(DataLoader, url) {
+ this._DataLoader = DataLoader;
+ this._url = url;
+ this._data = [];
+};
+
+IriSP.Serializer.prototype.serialize = function(data) { };
+IriSP.Serializer.prototype.deserialize = function(data) {};
+
+IriSP.Serializer.prototype.currentMedia = function() {
+};
+
+IriSP.Serializer.prototype.sync = function(callback) {
+ callback.call(this, this._data);
+};
+
+IriSP.SerializerFactory = function(DataLoader) {
+ this._dataloader = DataLoader;
+};
+
+IriSP.SerializerFactory.prototype.getSerializer = function(metadataOptions) {
+ /* This function returns serializer set-up with the correct
+ configuration - takes a metadata struct describing the metadata source
+ */
+
+ if (metadataOptions === undefined)
+ /* return an empty serializer */
+ return IriSP.Serializer("", "");
+
+ switch(metadataOptions.type) {
+ case "json":
+ return new IriSP.JSONSerializer(this._dataloader, metadataOptions.src);
+ break;
+
+ case "dummy": /* only used for unit testing - not defined in production */
+ return new IriSP.MockSerializer(this._dataloader, metadataOptions.src);
+ break;
+
+ case "empty":
+ return new IriSP.Serializer("", "empty");
+ break;
+
+ default:
+ return undefined;
+ }
+};
+/* site.js - all our site-dependent config : player chrome, cdn locations, etc...*/
+
+IriSP.defaults = {};
+
+/* these objects are filled by configureDefaults. The function doesn't overwrite
+ defaults that were originally defined by the user.
+*/
+IriSP.lib = {};
+
+/* We need to define those so that the individual settings can be overwritten */
+IriSP.widgetsDefaults = {};
+
+IriSP.paths = {};
+
+IriSP.libdir = "/metadataplayer/src/js/libs/";
+IriSP.jwplayer_swf_path = "/metadataplayer/test/libs/player.swf";
+IriSP.platform_url = "http://localhost/pf";
+IriSP.default_templates_vars = { };
+
+/** ugly ugly ugly ugly - returns an object defining
+ the paths to the libs
+ We need it that way cause it's called at runtime by
+ IriSP.configureDefaults.
+*/
+IriSP.defaults.lib = function(libdir) {
+ if (IriSP.null_or_undefined(libdir))
+ libdir = IriSP.libdir;
+
+ return {
+ jQuery : "http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.js",
+ jQueryUI : "http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.17/jquery-ui.js",
+ jQueryToolTip : "http://cdn.jquerytools.org/1.2.4/all/jquery.tools.min.js",
+ swfObject : "http://ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js",
+ cssjQueryUI : "http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.4/themes/base/jquery-ui.css",
+ popcorn : libdir + "popcorn.js",
+ jwplayer : libdir + "jwplayer.js",
+ popcornReplacement: libdir + "pop.js",
+ raphael: libdir + "raphael.js",
+ jquery_sparkline: libdir + "jquery.sparkline.js",
+ "popcorn.mediafragment" : libdir + "popcorn.mediafragment.js",
+ "popcorn.code" : libdir + "popcorn.code.js",
+ "popcorn.jwplayer": libdir + "popcorn.jwplayer.js",
+ "popcorn.youtube": libdir + "popcorn.youtube.js"
+ };
+};
+
+//Configuration for the player and utility functions.
+// No need to have them configured at runtime
+IriSP.config = {};
+
+IriSP.config.shortener = {
+ // function to call to shorten an url.
+ //shortening_function : IriSP.platform_shorten_url
+};
+
+IriSP.defaults.widgetsDefaults = function(platform_url) {
+ if (IriSP.null_or_undefined(platform_url))
+ platform_url = IriSP.platform_url;
+
+ return {
+ "LayoutManager" : {spacer_div_height : "0px" },
+ "PlayerWidget" : {},
+ "AnnotationsWidget": {
+ "share_text" : "I'm watching ",
+ "fb_link" : "http://www.facebook.com/share.php?u=",
+ "tw_link" : "http://twitter.com/home?status=",
+ "gplus_link" : ""
+ },
+
+ "TweetsWidget" : {
+ default_profile_picture : "https://si0.twimg.com/sticky/default_profile_images/default_profile_1_normal.png",
+ tweet_display_period: 10000 // how long do we show a tweet ?
+
+ },
+ "SliderWidget" : {
+ minimize_period: 850 // how long does the slider stays maximized after the user leaves the zone ?
+ },
+ "createAnnotationWidget" : {
+ keywords: ["#faux-raccord", "#mot-clef"],
+ polemic_mode: true, /* enable polemics ? */
+ /* polemics - the corresponding class names defined in the css should be for instance :
+ Ldt-createAnnotation-polemic-plusplus for plusplus
+ Ldt-createAnnotation-polemic-equalequal for equalequal, etc.
+ */
+ polemics: {"++" : "positive", "--" : "negative", "==" : "reference", "??" : "question"},
+ cinecast_version: true /* put to false to enable the platform version, true for the festival cinecast one. */
+ },
+ "SparklineWidget" : {
+ column_width: 10 // the width of a column in pixels.
+ },
+ "Main" : {
+ autoplay: true
+ },
+ "AnnotationsListWidget" : {
+ ajax_mode: true, /* use ajax to get information about the annotations.
+ if set to false, only search in the annotations for the
+ current project. */
+ /* the platform generates some funky urls. We replace them afterwards to point to the
+ correct place - this setting will probably be overwritten by the platform
+ implementers.
+ Note that the player has to replace the variables between {{ and }} by its own values.
+ */
+ ajax_url: platform_url + "/ldtplatform/api/ldt/segments/{media}/{begin}/{end}",
+
+ ajax_granularity: 10000, /* how much ms should we look before and after the
+ current timecode */
+
+ project_url: platform_url + "/ldtplatform/ldt/front/player/" /* the beginning
+ of a link to the
+ new front */
+ }
+ };
+};
+
+/*
+Override this if you want to change the info the player receives about the user.
+It's typically overrided in server-side templates with user-specific data.
+
+IriSP.user = {
+ "name" : "loic",
+ "avatar" : "http://a1.twimg.com/profile_images/39270812/loicempuria_normal.jpg"
+};
+*/
+
+IriSP.defaults.paths = {
+// "imgs": "/tweetlive/res/metadataplayer/src/css/imgs"
+ "imgs": "/metadataplayer/src/css/imgs"
+};
+
+IriSP.defaults.default_templates_vars = function() {
+ return {
+ "img_dir" : IriSP.paths.imgs
+ };
+}
+
+/* the widget classes and definitions */
+
+/**
+ * @class Widget is an "abstract" class. It's mostly used to define some properties common to every widget.
+ *
+ * Note that widget constructors are never called directly by the user. Instead, the widgets are instantiated by functions
+ * defined in init.js
+ *
+ * @constructor
+ * @param Popcorn a reference to the popcorn Object
+ * @param config configuration options for the widget
+ * @param Serializer a serializer instance from which the widget reads data fromCharCode
+*/
+IriSP.Widget = function(Popcorn, config, Serializer) {
+
+ if (config === undefined || config === null) {
+ config = {}
+ }
+
+ this._Popcorn = Popcorn;
+ this._config = config;
+ this._serializer = Serializer;
+
+ if (config.hasOwnProperty("container")) {
+ this._id = config.container;
+ this.selector = IriSP.jQuery("#" + this._id);
+ }
+
+ if (config.hasOwnProperty("spacer")) {
+ this._spacerId = config.spacer;
+ this.spacer = IriSP.jQuery("#" + this._spacerId);
+ }
+
+
+ if (config.hasOwnProperty("width")) {
+ // this.width and not this._width because we consider it public.
+ this.width = config.width;
+ }
+
+ if (config.hasOwnProperty("height")) {
+ this.height = config.height;
+ }
+
+ if (config.hasOwnProperty("heightmax")) {
+ this.heightmax = config.heightmax;
+ }
+
+ if (config.hasOwnProperty("widthmax")) {
+ this.widthmax = config.widthmax;
+ }
+
+ if (config.hasOwnProperty("layoutManager")) {
+ this.layoutManager = config.layoutManager;
+ }
+
+};
+
+/**
+ * This method responsible of drawing a widget on screen.
+ */
+IriSP.Widget.prototype.draw = function() {
+ /* implemented by "sub-classes" */
+};
+
+/**
+ * Optional method if you want your widget to support redraws.
+ */
+IriSP.Widget.prototype.redraw = function() {
+ /* implemented by "sub-classes" */
+};
+/* modules are non-graphical entities, similar to widgets */
+
+IriSP.Module = function(Popcorn, config, Serializer) {
+
+ if (config === undefined || config === null) {
+ config = {}
+ }
+
+ this._Popcorn = Popcorn;
+ this._config = config;
+ this._serializer = Serializer;
+};
+/* layout.js - very basic layout management */
+
+/**
+ @class a layout manager manages a div and the layout of objects
+ inside it.
+*/
+IriSP.LayoutManager = function(options) {
+ this._Popcorn = null;
+ this._widgets = [];
+
+ this._div = "LdtPlayer";
+ this._width = 640;
+
+ if (options === undefined) {
+ options = {};
+ };
+
+ if (options.hasOwnProperty('container')) {
+ this._div = options.container;
+ }
+
+ if (options.hasOwnProperty('width')) {
+ this._width = options.width;
+ }
+
+ if (options.hasOwnProperty('height')) {
+ this._height = options.height;
+ }
+
+ /* this is a shortcut */
+ this.selector = IriSP.jQuery("#" + this._div);
+
+ this.selector.css("width", this._width);
+
+ if (this._height !== undefined)
+ this.selector.css("height", this._height);
+};
+
+/**
+ Set the popcorn instance used by the manager.
+
+ we need this special setter because of a chicken and egg problem :
+ we want the manager to use popcorn but the popcorn div will be managed
+ by the manager. So we need a way to set the instance the manager uses
+*/
+
+IriSP.LayoutManager.prototype.setPopcornInstance = function(popcorn) {
+ this._Popcorn = popcorn;
+}
+
+/** create a subdiv with an unique id, and a spacer div as well.
+ @param widgetName the name of the widget.
+ @return an array of the form [createdivId, spacerdivId].
+*/
+IriSP.LayoutManager.prototype.createDiv = function(widgetName) {
+ if (typeof(widgetName) === "undefined")
+ widgetName = "";
+
+ var newDiv = IriSP.guid(this._div + "_widget_" + widgetName + "_");
+ var spacerDiv = IriSP.guid("LdtPlayer_spacer_");
+ this._widgets.push([widgetName, newDiv]);
+
+ var divTempl = "
");
+
+ if (options.hasOwnProperty("width"))
+ IriSP.jQuery("#" + containerDiv).css("width", options.width);
+
+ if (options.hasOwnProperty("height"))
+ IriSP.jQuery("#" + containerDiv).css("height", options.height);
+
+ pop = Popcorn("#" + tmpId);
+ break;
+
+ case "jwplayer":
+ var opts = IriSP.jQuery.extend({}, options);
+ delete opts.container;
+ delete opts.type;
+
+
+ /* Try to guess options.file and options.streamer only if file and streamer
+ are not already defined in the configuration */
+ if (options.provider === "rtmp" && !opts.hasOwnProperty("file") && !opts.hasOwnProperty("streamer")) {
+ /* exit if we can't access the metadata */
+ if (typeof(IriSP.__jsonMetadata) === "undefined") {
+ break;
+ };
+
+ // the json format is totally illogical
+ //opts.streamer = IriSP.__jsonMetadata["medias"][0]["meta"]["item"]["value"];
+ //var source = IriSP.__jsonMetadata["medias"][0]["href"];
+
+ // the source if a full url but jwplayer wants an url relative to the
+ // streamer url, so we've got to remove the common part.
+ //opts.file = source.slice(opts.streamer.length);
+
+ /* sometimes we get served a file with a wrong path and streamer.
+ as a streamer is of the form rtmp://domain/path/ and the media is
+ the rest, we uglily do this :
+ */
+ opts.file = "";
+ opts.streamer = "";
+ var fullPath = IriSP.__jsonMetadata["medias"][0]["href"];
+
+ /* files can either use href or url to refer to the stream */
+ if (IriSP.null_or_undefined(fullPath)) {
+ fullPath = IriSP.__jsonMetadata["medias"][0]["url"];
+ }
+
+ if (IriSP.null_or_undefined(fullPath)) {
+ console.log("no url or href field defined in the metadata.");
+ }
+
+ var pathSplit = fullPath.split('/');
+
+ for (var i = 0; i < pathSplit.length; i++) {
+ if (i < 4) {
+ opts.streamer += pathSplit[i] + "/";
+ } else {
+ opts.file += pathSplit[i];
+ /* omit the last slash if we're on the last element */
+ if (i < pathSplit.length - 1)
+ opts.file += "/";
+ }
+ }
+ } else {
+ /* other providers type, video for instance -
+ pass everything as is */
+ }
+
+ if (!options.hasOwnProperty("flashplayer")) {
+ opts.flashplayer = IriSP.jwplayer_swf_path;
+ }
+
+ if (!options.hasOwnProperty("controlbar.position")) {
+ opts["controlbar.position"] = "none";
+ }
+
+ pop = new IriSP.PopcornReplacement.jwplayer("#" + containerDiv, opts);
+ break;
+
+ case "youtube":
+ var opts = IriSP.jQuery.extend({}, options);
+ delete opts.container;
+ opts.controls = 0;
+ opts.autostart = false;
+ templ = "width: {{width}}px; height: {{height}}px;";
+ var str = Mustache.to_html(templ, {width: opts.width, height: opts.height});
+ // Popcorn.youtube wants us to specify the size of the player in the style attribute of its container div.
+ IriSP.jQuery("#" + containerDiv).attr("style", str);
+
+ pop = Popcorn.youtube("#" + containerDiv, opts.video, opts);
+ break;
+
+ case "dailymotion":
+ pop = new IriSP.PopcornReplacement.dailymotion("#" + containerDiv, options);
+ break;
+
+ case "allocine":
+ /* pass the options as-is to the allocine player and let it handle everything */
+ pop = new IriSP.PopcornReplacement.allocine("#" + containerDiv, options);
+ break;
+
+ default:
+ pop = undefined;
+ };
+
+ return pop;
+};
+
+/** Configure the gui and instantiate the widgets passed as parameters
+ @param guiOptions the gui object as seen in the examples.
+ */
+IriSP.configureWidgets = function (popcornInstance, layoutManager, guiOptions) {
+
+ var serialFactory = new IriSP.SerializerFactory(IriSP.__dataloader);
+ var params = {width: guiOptions.width, height: guiOptions.height};
+
+ var default_options = guiOptions.default_options;
+ if (IriSP.null_or_undefined(default_options))
+ default_options = {};
+
+ var ret_widgets = [];
+ var index;
+
+ for (index = 0; index < guiOptions.widgets.length; index++) {
+ var widget = IriSP.instantiateWidget(popcornInstance, serialFactory, layoutManager, guiOptions.widgets[index], default_options);
+
+ ret_widgets.push(widget);
+ };
+
+ return ret_widgets;
+};
+
+/** configure modules. @see configureWidgets */
+IriSP.configureModules = function (popcornInstance, modulesList) {
+ if (IriSP.null_or_undefined(modulesList))
+ return;
+
+ var serialFactory = new IriSP.SerializerFactory(IriSP.__dataloader);
+ var ret_modules = [];
+ var index;
+
+ for (index = 0; index < modulesList.length; index++) {
+ var moduleConfig = modulesList[index];
+
+ var serializer = serialFactory.getSerializer(moduleConfig.metadata);
+ var module = new IriSP[moduleConfig.type](popcornInstance, moduleConfig, serializer);
+ ret_modules.push(module);
+ };
+
+ return ret_modules;
+};
+
+/** instantiate a widget - only called by configureWidgets, never by the user. Handles widget
+ dependencies.
+ @param popcornInstance popcorn instance the widget will user
+ @param serialFactory serializer factory to instantiate the widget with
+ @param layoutManager layout manager
+ @param widgetConfig configuration options for the widget
+ @param defaultOptions a dictionnary with some options defined for every widget.
+ */
+IriSP.instantiateWidget = function(popcornInstance, serialFactory, layoutManager, widgetConfig, defaultOptions) {
+
+ if (IriSP.null_or_undefined(defaultOptions))
+ defaultOptions = {};
+
+ widgetConfig = IriSP.underscore.defaults(widgetConfig, defaultOptions);
+
+ var arr = IriSP.jQuery.extend({}, widgetConfig);
+
+ /* create a div for those widgets who didn't already specify a container; */
+ if (!arr.hasOwnProperty("container")) {
+ /* create div returns us a container for the widget and a spacer */
+ var ret = layoutManager.createDiv(widgetConfig.type);
+ var container = ret[0];
+ var spacer = ret[1];
+ arr.container = container;
+ arr.spacer = spacer;
+ arr.layoutManager = layoutManager;
+ }
+ var serializer = serialFactory.getSerializer(widgetConfig.metadata);
+
+ if (typeof serializer == "undefined")
+ debugger;
+
+ // instantiate the object passed as a string
+ var widget = new IriSP[widgetConfig.type](popcornInstance, arr, serializer);
+
+ if (widgetConfig.hasOwnProperty("requires")) {
+ // also create the widgets this one depends on.
+ // the dependency widget is available in the parent widget context as
+ // this.WidgetName (for instance, this.TipWidget);
+
+ var i = 0;
+ for(i = 0; i < widgetConfig.requires.length; i++) {
+ var widgetName = widgetConfig.requires[i]["type"];
+ widget[widgetName] = IriSP.instantiateWidget(popcornInstance, serialFactory, layoutManager, widgetConfig.requires[i], defaultOptions);
+ }
+ }
+
+ serializer.sync(IriSP.wrap(widget, function() { this.draw(); }));
+ return widget;
+};
+
+/** Go through the defaults to set a reasonable value */
+IriSP.configureDefaults = function(libdir, platform_url) {
+ /* the defaults configuration is messy and complicated. There are two things to know :
+ - we want to allow overwriting of defaults - that's why we have IriSP.widgetDefaults
+ and IriSP.defaults.widgetDefaults. The first is filled by the embedder and then fleshed out
+ with the contents of the first. We use underscore.defaults for that, but there's one problem with
+ this function : it doesn't work recursively.
+ - we need to compute some values at runtime instead of at compile time
+ */
+
+ IriSP.lib = IriSP.underscore.defaults(IriSP.lib, IriSP.defaults.lib(libdir));
+
+ /* get the factory defaults for the widgets and merge them with the default the user
+ may have defined
+ */
+ var factory_defaults = IriSP.defaults.widgetsDefaults(platform_url);
+ for(var widget in factory_defaults) {
+
+ /* create the object if it doesn't exists */
+ if (IriSP.null_or_undefined(IriSP.widgetsDefaults[widget]))
+ IriSP.widgetsDefaults[widget] = {};
+
+ IriSP.widgetsDefaults[widget] = IriSP.underscore.defaults(IriSP.widgetsDefaults[widget], factory_defaults[widget]);
+ }
+
+ IriSP.paths = IriSP.underscore.defaults(IriSP.paths, IriSP.defaults.paths);
+ IriSP.default_templates_vars = IriSP.underscore.defaults(IriSP.default_templates_vars,
+ IriSP.defaults.default_templates_vars());
+
+ if (IriSP.null_or_undefined(IriSP.user))
+ IriSP.user = {};
+
+ IriSP.user = IriSP.underscore.defaults(IriSP.user, IriSP.defaults.user());
+};
+
+/** single point of entry for the metadataplayer */
+IriSP.initPlayer = function(config, metadata_url, libdir, platform_url) {
+ IriSP.configureDefaults(libdir, platform_url);
+ IriSP.loadLibs(IriSP.lib, config, metadata_url,
+ function() {
+
+ var layoutManager = new IriSP.LayoutManager(config.gui);
+
+ var pop = IriSP.configurePopcorn(layoutManager, config.player);
+
+ var widgets = IriSP.configureWidgets(pop, layoutManager, config.gui);
+ var modules = IriSP.configureModules(pop, config.modules);
+ });
+};/* 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() {
+ if (_this.player) {
+ console.log("ask play _this.player = " + _this.player);
+ return _this.player.sendToActionScript("play");
+ } else {
+ return false;
+ }
+ },
+ pause : function() {
+ if (_this.player) {
+ console.log("ask pause _this.player = " + _this.player);
+ return _this.player.sendToActionScript("pause");
+ } else {
+ return false;
+ }
+ },
+ getPosition : function() {
+ if (_this.player) {
+ return _this.player.sendToActionScript("getSeek","return");
+ } else {
+ return 0;
+ }
+ },
+ seek : function(pos) {
+ if (_this.player) {
+ return _this.player.sendToActionScript("seek",pos);
+ } else {
+ return false;
+ }
+ },
+ getMute : function() {
+ if (_this.player) {
+ return _this.player.sendToActionScript("getMute","return");
+ } else {
+ return false;
+ }
+ },
+ setMute : function(p) {
+ if (_this.player) {
+ //return p ? _this.player.sendToActionScript("setMute") : _this.player.sendToActionScript("setMute");
+ _this.player.sendToActionScript("setMute");
+ } else {
+ return false;
+ }
+ }
+ }
+
+ window.onReady = IriSP.wrap(this, this.ready);
+ //NOT CALLED window.onAllocineStateChange = IriSP.wrap(this, this.stateHandler);
+ window.onTime = IriSP.wrap(this, this.progressHandler);
+
+ var fv = "adVast=false&lg=fr_cinecast&autoPlay="+options.autoPlay+"&directVideoTitle= &urlAcData="+options.urlAcData+"&directVideoPath="+options.directVideoPath+"&host=http://allocine.fr";
+ console.log("fv = " + fv);
+
+ var params = {
+ "allowScriptAccess" : "always",
+ "wmode": "opaque",
+ "flashvars" : fv
+ };
+ var atts = {
+ id : this.container
+ };
+ swfobject.embedSWF(options.acPlayerUrl, this.container, options.width, options.height, "8", 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.addEventListener("onVideoProgress", "onAllocineVideoProgress");
+ this.player.cueVideoByUrl(this._options.video);
+ this.callbacks.onReady();
+};
+
+IriSP.PopcornReplacement.allocine.prototype.progressHandler = function(progressInfo) {
+ this.callbacks.onTime({
+ position: progressInfo.mediaTime
+ });
+}
+
+
+IriSP.PopcornReplacement.allocine.prototype.stateHandler = function(state) {
+
+ switch(state) {
+ case 1:
+ this.callbacks.onPlay();
+ break;
+
+ case 2:
+ this.callbacks.onPause();
+ break;
+
+ case 3:
+ this.callbacks.onSeek({
+ position: this.player.getCurrentTime()
+ });
+ break;
+
+ /*
+ case 5:
+ this.callbacks.onReady();
+ 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) {
+ console.log("Calling");
+ /* 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() {
+ 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) {
+ return p ? _this.player.mute() : _this.player.unMute();
+ } else {
+ return false;
+ }
+ }
+ }
+
+ window.onDailymotionPlayerReady = IriSP.wrap(this, this.ready);
+ window.onDailymotionStateChange = IriSP.wrap(this, this.stateHandler);
+ window.onDailymotionVideoProgress = IriSP.wrap(this, this.progressHandler);
+
+ 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.ready = function() {
+
+ this.player = document.getElementById(this.container);
+
+ this.player.addEventListener("onStateChange", "onDailymotionStateChange");
+ this.player.addEventListener("onVideoProgress", "onDailymotionVideoProgress");
+ this.player.cueVideoByUrl(this._options.video);
+
+ this.callbacks.onReady();
+};
+
+IriSP.PopcornReplacement.dailymotion.prototype.progressHandler = function(progressInfo) {
+
+ this.callbacks.onTime({
+ position: progressInfo.mediaTime
+ });
+}
+
+IriSP.PopcornReplacement.dailymotion.prototype.stateHandler = function(state) {
+
+ switch(state) {
+ case 1:
+ this.callbacks.onPlay();
+ break;
+
+ case 2:
+ this.callbacks.onPause();
+ break;
+
+ case 3:
+ this.callbacks.onSeek({
+ position: this.player.getCurrentTime()
+ });
+ break;
+
+ /*
+ case 5:
+ this.callbacks.onReady();
+ 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);
+
+ this.media.duration = options.duration; /* optional */
+
+ /* Définition des fonctions de l'API - */
+ this.playerFns = {
+ play: function() { return jwplayer(this.container).play(); },
+ pause: function() { return jwplayer(this.container).pause(); },
+ getPosition: function() { return jwplayer(this.container).getPosition(); },
+ seek: function(pos) { return jwplayer(this.container).seek(pos); },
+ getMute: function() { return jwplayer(this.container).getMute() },
+ setMute: function(p) { return jwplayer(this.container).setMute(p); }
+ }
+
+ options.events = this.callbacks;
+
+ jwplayer(this.container).setup(options);
+};
+
+IriSP.PopcornReplacement.jwplayer.prototype = new IriSP.PopcornReplacement.player("", {});
+/* embed module - listens and relay hash changes to a parent window. */
+
+IriSP.EmbedModule = function(Popcorn, config, Serializer) {
+ IriSP.Module.call(this, Popcorn, config, Serializer);
+
+ window.addEventListener('message', IriSP.wrap(this, this.handleMessages), false);
+ this._Popcorn.listen("IriSP.Mediafragment.hashchange", IriSP.wrap(this, this.relayChanges));
+};
+
+IriSP.EmbedModule.prototype = new IriSP.Module();
+
+IriSP.EmbedModule.prototype.handleMessages = function(e) {
+ if (e.data.type === "hashchange") {
+ window.location.hash = e.data.value;
+ }
+};
+
+IriSP.EmbedModule.prototype.relayChanges = function(newHash) {
+ window.parent.postMessage({type: "hashchange", value: newHash}, "*");
+ return;
+};/* mediafragment module */
+
+IriSP.MediaFragment = function(Popcorn, config, Serializer) {
+ IriSP.Module.call(this, Popcorn, config, Serializer);
+
+ this.mutex = false; /* a mutex because we access the url from two different functions */
+
+ this._Popcorn.listen( "loadedmetadata", IriSP.wrap(this,this.advanceTime));
+ this._Popcorn.listen( "pause", IriSP.wrap(this,this.updateTime));
+ this._Popcorn.listen( "seeked", IriSP.wrap(this,this.updateTime));
+ this._Popcorn.listen( "IriSP.PolemicTweet.click", IriSP.wrap(this,this.updateAnnotation));
+ this._Popcorn.listen( "IriSP.SegmentsWidget.click", IriSP.wrap(this,this.updateAnnotation));
+
+ window.onhashchange = IriSP.wrap(this, this.advanceTime);
+};
+
+IriSP.MediaFragment.prototype = new IriSP.Module();
+
+IriSP.MediaFragment.prototype.advanceTime = function() {
+ var url = window.location.href;
+
+ if ( url.split( "#" )[ 1 ] != null ) {
+ pageoffset = url.split( "#" )[1];
+
+ if ( pageoffset.substring(0, 2) === "t=") {
+ // timecode
+ if ( pageoffset.substring( 2 ) != null ) {
+ var offsettime = pageoffset.substring( 2 );
+ this._Popcorn.currentTime( parseFloat(offsettime) );
+
+ /* we have to trigger this signal manually because of a
+ bug in the jwplayer */
+ this._Popcorn.trigger("seeked", parseFloat(offsettime));
+ }
+ } else if ( pageoffset.substring(0, 3) === "id=") {
+ // annotation
+ var annotationId = pageoffset.substring( 3 );
+ // there's no better way than that because
+ // of possible race conditions
+ this._serializer.sync(IriSP.wrap(this, function() {
+ this.lookupAnnotation.call(this, annotationId);
+ }));
+ }
+ }
+};
+
+/** handler for the seeked signal. It may have or may have not an argument.
+ @param time if not undefined, the time we're seeking to
+*/
+IriSP.MediaFragment.prototype.updateTime = function(time) {
+ if (this.mutex === true) {
+ return;
+ }
+
+ var history = window.history;
+ if ( !history.pushState ) {
+ return false;
+ }
+
+ if (IriSP.null_or_undefined(time) || typeof(time) != "number") {
+ var ntime = this._Popcorn.currentTime().toFixed(2)
+ } else {
+ var ntime = time.toFixed(2);
+ }
+
+ // used to relay the new hash to the embedder
+ this._Popcorn.trigger("IriSP.Mediafragment.hashchange", "#t=" + ntime);
+
+ splitArr = window.location.href.split( "#" )
+ history.replaceState( {}, "", splitArr[0] + "#t=" + ntime );
+};
+
+
+IriSP.MediaFragment.prototype.updateAnnotation = function(annotationId) {
+ var _this = this;
+ this.mutex = true;
+
+ var history = window.history;
+ if ( !history.pushState ) {
+ return false;
+ }
+
+
+ // used to relay the new hash to the embedder
+ this._Popcorn.trigger("IriSP.Mediafragment.hashchange", "#id=" + annotationId);
+
+ splitArr = window.location.href.split( "#" )
+ history.replaceState( {}, "", splitArr[0] + "#id=" + annotationId);
+
+
+ // reset the mutex afterwards to prevent the module from reacting to his own changes.
+ window.setTimeout(function() { _this.mutex = false }, 50);
+};
+
+// lookup and seek to the beginning of an annotation
+IriSP.MediaFragment.prototype.lookupAnnotation = function(annotationId) {
+ var _this = this;
+ this.mutex = true;
+
+ var annotation = undefined;
+ var annotations = this._serializer._data.annotations;
+
+ var i;
+ for (i = 0; i < annotations.length; i++) {
+ if (annotations[i].id === annotationId) {
+ annotation = annotations[i];
+ break;
+ }
+ }
+
+ if (typeof(annotation) !== "undefined") {
+ this._Popcorn.currentTime(annotation.begin / 1000);
+
+ /* we have to trigger this signal manually because of a
+ bug in the jwplayer */
+ this._Popcorn.trigger("seeked", annotation.begin / 1000);
+ this._Popcorn.trigger("IriSP.Mediafragment.showAnnotation", annotationId);
+ }
+
+ window.setTimeout(function() { _this.mutex = false }, 50);
+};
+IriSP.AnnotationsListWidget = function(Popcorn, config, Serializer) {
+ IriSP.Widget.call(this, Popcorn, config, Serializer);
+ this.__counter = 0;
+ this.__oldList = [];
+
+ this.ajax_mode = IriSP.widgetsDefaults["AnnotationsListWidget"].ajax_mode;
+ this.project_url = IriSP.widgetsDefaults["AnnotationsListWidget"].project_url;
+};
+
+
+IriSP.AnnotationsListWidget.prototype = new IriSP.Widget();
+
+IriSP.AnnotationsListWidget.prototype.clear = function() {
+};
+
+IriSP.AnnotationsListWidget.prototype.clearWidget = function() {
+};
+
+/** effectively redraw the widget - called by drawList */
+IriSP.AnnotationsListWidget.prototype.do_redraw = function(list) {
+ var widgetMarkup = IriSP.templToHTML(IriSP.annotationsListWidget_template, {annotations: list});
+ this.selector.html(widgetMarkup);
+};
+
+/** draw the annotation list */
+IriSP.AnnotationsListWidget.prototype.drawList = function(force_redraw) {
+ var _this = this;
+
+ var view_type = this._serializer.getContributions();
+ var annotations = this._serializer._data.annotations;
+ var currentTime = this._Popcorn.currentTime();
+
+ var list = [];
+
+ if (typeof(view_type) === "undefined") {
+ return;
+ }
+
+ for (i = 0; i < annotations.length; i++) {
+ var annotation = annotations[i];
+
+ /* filter the annotations whose type is not the one we want */
+ if (typeof(annotation.meta) !== "undefined" && typeof(annotation.meta["id-ref"]) !== "undefined"
+ && annotation.meta["id-ref"] !== view_type) {
+ continue;
+ }
+
+ /* only get the annotations happening in the current chapter */
+ if (!(annotation.begin <= currentTime * 1000 && annotation.end > currentTime * 1000)) {
+ continue;
+ }
+
+ var a = annotation;
+ var obj = {};
+
+ obj["id"] = a.id;
+ obj["title"] = a.content.title;
+ obj["desc"] = a.content.description;
+ obj["begin"] = IriSP.msToTime(annotation.begin);
+ obj["end"] = IriSP.msToTime(annotation.end);
+
+ list.push(obj);
+ }
+
+ var idList = IriSP.underscore.pluck(list, "id").sort();
+
+ if (idList.length !== this.__oldList.length) {
+ this.do_redraw(list);
+ }
+
+ var res = 1;
+ for (var i = 0; i < idList.length; i++) {
+ if (idList[i] !== this.__oldList[i])
+ res = 0;
+ break;
+ }
+
+ this.__oldList = idList; /* save for next call */
+
+ if (typeof(force_redraw) !== "undefined") {
+ this.do_redraw(list);
+ }
+
+ /* the two lists are equal, no need to redraw */
+ if (res === 1) {
+ return;
+ } else {
+ this.do_redraw(list);
+ }
+
+};
+
+IriSP.AnnotationsListWidget.prototype.ajaxRedraw = function(timecode) {
+
+ /* the seeked signal sometimes passes an argument - depending on if we're using
+ our popcorn lookalike or the real thing - if it's the case, use it as it's
+ more precise than currentTime which sometimes contains the place we where at */
+ if (IriSP.null_or_undefined(timecode) || typeof(timecode) != "number") {
+ var tcode = this._Popcorn.currentTime();
+ } else {
+ var tcode = timecode;
+ }
+
+
+ /* the platform gives us a special url - of the type : http://path/{media}/{begin}/{end}
+ we double the braces using regexps and we feed it to mustache to build the correct url
+ we have to do that because the platform only knows at run time what view it's displaying.
+ */
+
+ var platf_url = IriSP.widgetsDefaults["AnnotationsListWidget"].ajax_url
+ .replace(/\{/g, '{{').replace(/\}/g, '}}');
+ var media_id = this._serializer.currentMedia()["id"];
+ var duration = +this._serializer.currentMedia().meta["dc:duration"];
+
+ var begin_timecode = (Math.floor(tcode) - 300) * 1000;
+ if (begin_timecode < 0)
+ begin_timecode = 0;
+
+ var end_timecode = (Math.floor(tcode) + 300) * 1000;
+ if (end_timecode > duration)
+ end_timecode = duration;
+
+ var templ = Mustache.to_html(platf_url, {media: media_id, begin: begin_timecode,
+ end: end_timecode});
+
+ /* we create on the fly a serializer to get the ajax */
+ var serializer = new IriSP.JSONSerializer(IriSP.__dataloader, templ);
+ serializer.sync(IriSP.wrap(this, function(json) { this.processJson(json, serializer) }));
+};
+
+/** process the received json - it's a bit hackish */
+IriSP.AnnotationsListWidget.prototype.processJson = function(json, serializer) {
+ /* FIXME: DRY the whole thing */
+ var annotations = serializer._data.annotations;
+ if (IriSP.null_or_undefined(annotations))
+ return;
+
+ /*
+ commented in case we wanted to discriminate against some annotation types.
+ var view_types = serializer.getIds("Contributions");
+ */
+ var l = [];
+
+ var media = this._serializer.currentMedia()["id"];
+
+ for (i = 0; i < annotations.length; i++) {
+ var annotation = annotations[i];
+
+ /* filter the annotations whose type is not the one we want */
+ /* We want _all_ the annotations.
+ if (typeof(annotation.meta) !== "undefined" && typeof(annotation.meta["id-ref"]) !== "undefined"
+ && !IriSP.underscore.include(view_types, annotation.meta["id-ref"])) {
+ continue;
+ }
+ */
+ var a = annotation;
+ var obj = {};
+
+ obj["id"] = a.id;
+ obj["title"] = a.content.title;
+ obj["desc"] = a.content.description;
+ obj["begin"] = IriSP.msToTime(annotation.begin);
+ obj["end"] = IriSP.msToTime(annotation.end);
+
+ /* only if the annotation isn't present in the document create an
+ external link */
+ if (!this.annotations_ids.hasOwnProperty(obj["id"])) {
+ // braindead url; jacques didn't want to create a new one in the platform,
+ // so we append the cutting id to the url.
+ obj["url"] = this.project_url + "/" + media + "/" +
+ annotation.meta["project"] + "/" +
+ annotation.meta["id-ref"];
+
+ // obj["url"] = document.location.href.split("#")[0] + "/" + annotation.meta["project"];
+ }
+
+ l.push(obj);
+ }
+
+ this.do_redraw(l);
+};
+IriSP.AnnotationsListWidget.prototype.draw = function() {
+
+ /* build a table of the annotations present in the document for faster
+ lookup
+ */
+ this.annotations_ids = {};
+
+ var annotations = this._serializer._data.annotations;
+ var i = 0;
+ for(i = 0; i < annotations.length; i++) {
+ this.annotations_ids[annotations[i]["id"]] = 1;
+ }
+
+ this.drawList();
+
+ if (!this.ajax_mode) {
+ this._Popcorn.listen("IriSP.createAnnotationWidget.addedAnnotation", IriSP.wrap(this, function() { this.drawList(true); }));
+ this._Popcorn.listen("timeupdate", IriSP.wrap(this, this.redraw));
+ } else {
+ /* update the widget when the video has finished loading and when it's seeked and paused */
+ this._Popcorn.listen("seeked", IriSP.wrap(this, this.ajaxRedraw));
+ this._Popcorn.listen("loadedmetadata", IriSP.wrap(this, this.ajaxRedraw));
+ this._Popcorn.listen("paused", IriSP.wrap(this, this.ajaxRedraw));
+
+ this._Popcorn.listen("IriSP.createAnnotationWidget.addedAnnotation", IriSP.wrap(this, this.ajaxRedraw));
+ }
+
+};
+
+IriSP.AnnotationsListWidget.prototype.redraw = function() {
+ this.drawList();
+};IriSP.AnnotationsWidget = function(Popcorn, config, Serializer) {
+ IriSP.Widget.call(this, Popcorn, config, Serializer);
+ /* flag used when we're creating an annotation */
+ this._hidden = false;
+};
+
+
+IriSP.AnnotationsWidget.prototype = new IriSP.Widget();
+
+IriSP.AnnotationsWidget.prototype.clear = function() {
+ this.selector.find(".Ldt-SaTitle").text("");
+ this.selector.find(".Ldt-SaDescription").text("");
+ this.selector.find(".Ldt-SaKeywordText").text("");
+};
+
+IriSP.AnnotationsWidget.prototype.displayAnnotation = function(annotation) {
+ var title = annotation.content.title;
+ var description = annotation.content.description;
+ var keywords = "";
+ var begin = +annotation.begin / 1000;
+ var end = +annotation.end / 1000;
+ var duration = +this._serializer.currentMedia().meta["dc:duration"];
+ var tags = "";
+
+ var title_templ = "{{title}} - ( {{begin}} - {{end}} )";
+ var endstr = Mustache.to_html(title_templ, {title: title, begin: IriSP.secondsToTime(begin), end: IriSP.secondsToTime(end)});
+
+ this.selector.find(".Ldt-SaTitle").text(endstr);
+ this.selector.find(".Ldt-SaDescription").text(description);
+
+
+ if (!IriSP.null_or_undefined(annotation.tags) && !IriSP.null_or_undefined(this._serializer._data.tags)) {
+ /* save the tag id and keywords in a unique structure */
+ var tag_list = {};
+ for (var i = 0; i < this._serializer._data.tags.length; i++) {
+ var id = this._serializer._data.tags[i]["id"];
+ var keyword = this._serializer._data.tags[i]["meta"]["dc:title"];
+
+ tag_list[id] = keyword;
+ }
+
+ /* then browse the list of defined tags for the current annotation */
+ for (var i = 0; i < annotation.tags.length; i++) {
+ if (tag_list.hasOwnProperty(annotation.tags[i]["id-ref"]))
+ tags += tag_list[annotation.tags[i]["id-ref"]] + ", ";
+ }
+ }
+
+ tags = "Keywords: " + tags.slice(0, tags.length - 2);
+
+ this.selector.find(".Ldt-SaKeywords").text(tags);
+
+ // update sharing buttons
+ var defaults = IriSP.widgetsDefaults.AnnotationsWidget;
+ var text = defaults.share_text;
+ var fb_link = defaults.fb_link;
+ var tw_link = defaults.tw_link;
+ var gplus_link = defaults.gplus_link;
+ var url = document.location.href + "#id=" + annotation.id;
+ this.selector.find(".Ldt-fbShare").attr("href", IriSP.mkFbUrl(url, text));
+ this.selector.find(".Ldt-TwShare").attr("href", IriSP.mkTweetUrl(url, text));
+ this.selector.find(".Ldt-GplusShare").attr("href", IriSP.mkGplusUrl(url, text));
+};
+
+IriSP.AnnotationsWidget.prototype.clearWidget = function() {
+ /* retract the pane between two annotations */
+ this.selector.find(".Ldt-SaTitle").text("");
+ this.selector.find(".Ldt-SaDescription").text("");
+ this.selector.find(".Ldt-SaKeywordText").html("");
+ this.selector.find(".Ldt-ShowAnnotation").slideUp();
+};
+
+IriSP.AnnotationsWidget.prototype.draw = function() {
+ var _this = this;
+
+ var annotationMarkup = IriSP.templToHTML(IriSP.annotationWidget_template);
+ this.selector.append(annotationMarkup);
+
+ this._Popcorn.listen("IriSP.AnnotationsWidget.show",
+ IriSP.wrap(this, this.show));
+ this._Popcorn.listen("IriSP.AnnotationsWidget.hide",
+ IriSP.wrap(this, this.hide));
+
+ var legal_ids = [];
+ if (typeof(this._serializer.getChapitrage()) !== "undefined")
+ legal_ids.push(this._serializer.getChapitrage());
+ else
+ legal_ids = this._serializer.getNonTweetIds();
+
+ var annotations = this._serializer._data.annotations;
+ var i;
+
+ for (i in annotations) {
+ var annotation = annotations[i];
+ var begin = Math.round((+ annotation.begin) / 1000);
+ var end = Math.round((+ annotation.end) / 1000);
+
+ if (typeof(annotation.meta) !== "undefined" && typeof(annotation.meta["id-ref"]) !== "undefined"
+ && !IriSP.underscore.include(legal_ids, annotation.meta["id-ref"])) {
+ continue;
+ }
+
+
+ var conf = {start: begin, end: end,
+ onStart:
+ function(annotation) {
+ return function() {
+ _this.displayAnnotation(annotation);
+
+ } }(annotation),
+ onEnd:
+ function() { _this.clearWidget.call(_this); }
+ };
+ this._Popcorn = this._Popcorn.code(conf);
+ }
+
+};
+
+IriSP.AnnotationsWidget.prototype.hide = function() {
+ if (this._hidden == false) {
+ this.selector.hide();
+ this._hidden = true;
+ }
+};
+
+IriSP.AnnotationsWidget.prototype.show = function() {
+ if (this._hidden == true) {
+ this.selector.show();
+ this._hidden = false;
+ }
+};IriSP.ArrowWidget = function(Popcorn, config, Serializer) {
+ IriSP.Widget.call(this, Popcorn, config, Serializer);
+
+ this._oldAnnotation = null;
+ this._blockArrow = false;
+};
+
+
+IriSP.ArrowWidget.prototype = new IriSP.Widget();
+
+IriSP.ArrowWidget.prototype.clear = function() {
+
+};
+
+IriSP.ArrowWidget.prototype.clearWidget = function() {
+};
+
+IriSP.ArrowWidget.prototype.draw = function() {
+ var templ = Mustache.to_html(IriSP.arrowWidget_template, {});
+ this.selector.append(templ);
+ this._Popcorn.listen("timeupdate", IriSP.wrap(this, this.timeUpdateHandler));
+ this._Popcorn.listen("IriSP.ArrowWidget.blockArrow", IriSP.wrap(this, this.blockArrow));
+ this._Popcorn.listen("IriSP.ArrowWidget.releaseArrow", IriSP.wrap(this, this.releaseArrow));
+
+};
+
+IriSP.ArrowWidget.prototype.timeUpdateHandler = function(percents) {
+ if (this._blockArrow)
+ return;
+
+ var currentTime = this._Popcorn.currentTime();
+ var currentAnnotation = this._serializer.currentChapitre(currentTime);
+ if (IriSP.null_or_undefined(currentAnnotation)) {
+ var c_annots = this._serializer.currentAnnotation(currentTime)
+ if (c_annots.length != 0)
+ var currentAnnotation = c_annots[0]; // FIXME : use the others ?
+ else
+ return;
+ }
+
+ /* move the arrow only if the current annotation changes */
+ if (currentAnnotation != this._oldAnnotation) {
+ var begin = (+ currentAnnotation.begin) / 1000;
+ var end = (+ currentAnnotation.end) / 1000;
+
+ var duration = +this._serializer.currentMedia().meta["dc:duration"] / 1000;
+ var middle_time = (begin + end) / 2;
+ var percents = middle_time / duration;
+
+ // we need to apply a fix because the arrow has a certain length
+ // it's half the length of the arrow (27 / 2). We need to convert
+ // it in percents though.
+ var totalWidth = this.selector.width();
+ var pixels = percents * totalWidth;
+ var correction = (27 / 2);
+ var corrected_pixels = pixels - correction;
+
+ /* make sure that the arrow is aligned with the pattern
+ of the widget under it */
+ if (corrected_pixels % 3 != 0)
+ corrected_pixels -= (corrected_pixels % 3 - 1);
+
+ /* don't move out of the screen */
+ if (corrected_pixels <= 0)
+ corrected_pixels = 0;
+
+ if (corrected_pixels <= 15) {
+ this.selector.children(".Ldt-arrowWidget").removeClass("Ldt-arrowLeftEdge Ldt-arrowCenter Ldt-arrowRightEdge")
+ .addClass("Ldt-arrowLeftEdge");
+ } else if (corrected_pixels >= totalWidth - 25) {
+ this.selector.children(".Ldt-arrowWidget").removeClass("Ldt-arrowLeftEdge Ldt-arrowCenter Ldt-arrowRightEdge")
+ .addClass("Ldt-arrowRightEdge");
+ } else {
+ this.selector.children(".Ldt-arrowWidget").removeClass("Ldt-arrowLeftEdge Ldt-arrowCenter Ldt-arrowRightEdge")
+ .addClass("Ldt-arrowCenter");
+ }
+
+ this.selector.children(".Ldt-arrowWidget").animate({"left" : corrected_pixels + "px"});
+
+ this._oldAnnotation = currentAnnotation;
+ }
+};
+
+/** Block the arrow for instance when the user is annotating */
+IriSP.ArrowWidget.prototype.blockArrow = function() {
+ this._blockArrow = true;
+};
+
+IriSP.ArrowWidget.prototype.releaseArrow = function() {
+ this._blockArrow = false;
+};
+IriSP.createAnnotationWidget = function(Popcorn, config, Serializer) {
+ IriSP.Widget.call(this, Popcorn, config, Serializer);
+ this._hidden = true;
+ this.keywords = IriSP.widgetsDefaults["createAnnotationWidget"].keywords;
+
+ this.polemic_mode = IriSP.widgetsDefaults["createAnnotationWidget"].polemic_mode;
+ this.polemics = IriSP.widgetsDefaults["createAnnotationWidget"].polemics;
+
+ this.cinecast_version = IriSP.widgetsDefaults["createAnnotationWidget"].cinecast_version;
+ this.api_endpoint_template = IriSP.widgetsDefaults["createAnnotationWidget"].api_endpoint_template;
+
+ this.ids = {}; /* a dictionnary linking buttons ids to keywords */
+
+ /* variables to save the current position of the slicer */
+ if (this.cinecast_version) {
+ this.sliceLeft = 0;
+ this.sliceWidth = 0;
+ }
+};
+
+
+IriSP.createAnnotationWidget.prototype = new IriSP.Widget();
+
+IriSP.createAnnotationWidget.prototype.clear = function() {
+ this.selector.find(".Ldt-SaTitle").text("");
+ this.selector.find(".Ldt-SaDescription").text("");
+ this.selector.find(".Ldt-SaKeywordText").text("");
+};
+
+IriSP.createAnnotationWidget.prototype.draw = function() {
+ var _this = this;
+ var template_params = {cinecast_version: this.cinecast_version,
+ polemic_mode: this.polemic_mode};
+
+ if (!IriSP.null_or_undefined(IriSP.user) && !IriSP.null_or_undefined(IriSP.user.avatar))
+ template_params["user_avatar"] = IriSP.user.avatar;
+
+ var annotationMarkup = IriSP.templToHTML(IriSP.createAnnotationWidget_template,
+ template_params);
+
+ this.selector.append(annotationMarkup);
+
+ if (!this.cinecast_version)
+ this.selector.hide();
+ else {
+ this.showStartScreen();
+ }
+
+ // add the keywords.
+ for (var i = 0; i < this.keywords.length; i++) {
+ var keyword = this.keywords[i];
+ var id = IriSP.guid("button_");
+ var templ = IriSP.templToHTML("