/*
* This file is part of the TraKERS\Front IDILL package.
*
* (c) IRI <http://www.iri.centrepompidou.fr/>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

/*
 * Projet : TraKERS
 * Module : Front IDILL
 * Fichier : Timeline.js
 * 
 * Auteur : alexandre.bastien@iri.centrepompidou.fr
 * 
 * Fonctionnalités : Widget de la timeline du player incorporé dans le Front.
 */

/*
 The Timeline Widget fits right under the video
 * Est appelé dans les fichiers créant les widgets du metadataplayer.
 */

IriSP.Widgets.Timeline = function(player, config) {
    
    IriSP.Widgets.Widget.call(this, player, config);
    this.bindPopcorn("timeupdate","onTimeupdate");
    this.bindPopcorn("loadedmetadata","ready");
    this.bindPopcorn("markersready","onMarkersReady");
    this.timelineSelected = false;
    this.markerShowTime = 200;
    this.markerLastTime = 5000;
    this.markerBigShown = false;
    this.currentMarkerIdx = -1;
    this.previousMarkerIdx = -1;
    this.hideTimeout;
    this.currentMode = "VIDEO";
    this.paused = false
    this.top_epsilon = 0;
    this.imgDir = "player/img/";
    this.markersDir = "pictos/small/";
    this.player = player;
    this.isCurrentlyInASearchByGesture = false;
    this.mouseInteractions = false;
    
    //Id du marqueur enregistré.
    this.currentMarkerId;
    
    //Gestures et noms par défaut.
    this.gestures = ["fall", "jump", "circle", "screw", "bend", "arc", "knee-up", "right-angle", "wave", "no-motion", "contact", "up-down"];
    this.gesturesText = ["chute", "saut", "rotation", "rotation de groupe", "inclinaison", "port de bras", "levé de genou", "angle droit", "ondulation", "immobilité", "contact", "de haut en bas"];
    
	var _this = this;
    this.annotations = this.source.getAnnotations().filter(function(annotation)
	{
		return _this.isGesture(annotation, null, _this.gestures);
	});
};

/*
 * Constructeur du Widget.
*/
IriSP.Widgets.Timeline.prototype = new IriSP.Widgets.Widget();

/*
 * Indique si l'annotation passée en paramètre référence une gesture utilisée dans ce Front.
*/
IriSP.Widgets.Timeline.prototype.isGesture = function(element, index, array)
{
    return ($.inArray(element.annotationType.contents.title, array) > -1);
}

/*
 * Spécifie si on est en événements souris ou non.
 * Est appelé dans le fichier :
 * mosaic > fonction onMarkersReady.
*/
IriSP.Widgets.Timeline.prototype.setMouseInteractions = function(mouseInteractions)
{
    this.mouseInteractions = mouseInteractions;
    
    if(mouseInteractions)
    {
        this.markersDir += 'MI/';
    }
}

/*
 * Spécifie la langue pour l'affichage des marqueurs.
 * Est appelé dans le fichier :
 * mosaic > fonction onMarkersReady.
*/
IriSP.Widgets.Timeline.prototype.setLang = function(gesturesText)
{
    this.gesturesText = gesturesText;
}

/*
 * Fonction associée à l'événement : les marqueurs sont prêts.
 * Est appelé dans le fichier :
 * mosaic > fonction onPlayerLoad.
*/
IriSP.Widgets.Timeline.prototype.onMarkersReady = function() {}

/*
 * Paramètres par défaut de la timeline.
*/
IriSP.Widgets.Timeline.prototype.defaults = {
    minimized_height : 114,//44,
    maximized_height : 114,
    middle_height: 10,//4,
    timelineBorderLength : 6,
    minimize_timeout : 1500 // time before minimizing timeline after mouseout
};

/*
 * Fonction appelée pour dessiner la timeline.
*/
IriSP.Widgets.Timeline.prototype.draw = function() {
    this.$timeline = IriSP.jQuery('<div>')
        .addClass("Ldt-Timeline")
        .css(this.calculateTimelineCss(this.minimized_height));
    this.$timelineMiddle = IriSP.jQuery('<div>')
        .addClass("Ldt-TimelineMiddle")
        .css(this.calculateTimelineMiddleCss(this.minimized_height, this.middle_height));
    
    //On l'ajoute au widget.
    this.$.append(this.$timeline);
    this.$.append(this.$timelineMiddle);
    
    var _this = this;
    
    //On définit le slider.
    this.$timeline.slider({
        range: "min",
        value: 0,
        min: 0,
        max: this.source.getDuration().milliseconds,
        slide: function(event, ui) {
            if(_this.player.popcorn)
            {
                _this.player.popcorn.currentTime(Math.floor(ui.value/1000));
                _this.player.popcorn.trigger("IriSP.Mediafragment.setHashToTime");
            }
            
            //On supprime le marqueur précédemment affiché.
            if(_this.previousMarkerIdx > -1)
            {
                var previousMarker = IriSP.jQuery("#" + _this.annotations[_this.previousMarkerIdx].id.replace(":", "_"));
                _this.hideMarkerBig(previousMarker);
            }
        }
    });
    
    this.$handle = this.$timeline.find('.ui-slider-handle');
    
    this.$handle.css(this.calculateHandleCss(this.minimized_height));
    
    //On wrapp mouseover et mouse out.
    this.$.mouseover(this.functionWrapper("onMouseover")).mouseout(this.functionWrapper("onMouseout"));
    //On bind keypress.
    IriSP.jQuery('body').keypress(function(evt) {_this.keyPress(evt)});
    
    this.maximized = false;
    this.timeoutId = false;
};

/*
 * Starts playing the video when it's ready.
*/
IriSP.Widgets.Timeline.prototype.ready = function() {
    this.player.popcorn.play();
    this.player.popcorn.mute();
    this.processMarkers();
}

/*
 * Met à l'échelle une valeur de [A, B] vers [C, D].
 * Est appelé dans les fichiers :
 * pointers > fonction pointersTimelineSelection.
 * search > fonction searchFilter.
 * Timeline > fonctions processMarkers, onTimeupdate et timeDisplayUpdater.
*/
IriSP.Widgets.Timeline.prototype.scaleIntervals = function(A, B, C, D, val) {
    if(A == B)
	{
		return (D - C) / 2;
	}
	else
	{
		return (D - C) * (val - A) / (B - A) + C;
	}
}

/*
 * On calcule les marqueurs.
 * Est appelé dans le fichier :
 * Timeline > fonction ready.
*/
IriSP.Widgets.Timeline.prototype.processMarkers = function() {
    var _this = this;
    var markers = "";
    //On calcule la position en Y de la timeline.
    var timelineMiddleTop = this.$timelineMiddle.position().top;
    
    //Pour toutes les annotations, on crée les marqueurs.
    for(var i = 0 ; i < this.annotations.length ; i++)
    {
        markers += "<div class='Ldt-Marker' id='" + this.annotations[i].id.replace(":", "_") + "'></div>";
    }
    
    //On les ajoute.
    this.$.append(markers);
    var markerHeight = IriSP.jQuery(".Ldt-Marker").height();
    IriSP.jQuery(".Ldt-Marker").css("z-align", "150");
    
    //Pour toutes les annotations.
    for(var i = 0 ; i < this.annotations.length ; i++)
    {
        //On les place sur la timeline.
        IriSP.jQuery("#" + this.annotations[i].id.replace(":", "_")).css(
        {
            top: timelineMiddleTop + "px",
            left: Math.floor(+this.scaleIntervals(0, this.source.getDuration().getSeconds(), 0, this.$timeline.width(), this.annotations[i].begin/1000) + this.$timeline.position().left) + "px",
            "margin-top": (-_this.$timeline.height()/2 - markerHeight/2) - this.top_epsilon + "px"
        });
    }
    
    //On lance l'événement pour dire à popcorn que les marqueurs sont utilisables.
    this.player.popcorn.trigger("markersready");
}

/*
 * Fonction de recherche par gestures.
 * Est appelé dans les fichiers :
 * mosaic > fonctions onMouseUp, manageControlEvents et onMarkersReady.
 * curvesDetector > fonction updateDists.
*/
IriSP.Widgets.Timeline.prototype.searchByGesture = function(typeName)
{
    //Si le type existe.
    if(typeName != '' || typeName != undefined)
    {
        if(_.include(this.gestures, typeName))
        {
            //On entre en mode recherche et on affiche les marqueurs sélectionnés.
            this.currentMode = "SEARCH";
            this.hideMarkersSearch(typeName);
            this.isCurrentlyInASearchByGesture = true;
        }
    }
}

/*
 * Fonction de suppression de recherche par gesures.
 * Est appelé dans le fichier :
 * pointers > checkIfPointerIsOnSearchNotification et removeSearchNotificationIfOnIt.
*/
IriSP.Widgets.Timeline.prototype.removeSearchByGesture = function()
{
    this.hideMarkersSearch();
    this.isCurrentlyInASearchByGesture = false;
}

/*
 * Place le curseur sur la timeline en fonction de la touche pressée.
 * Est appelé dans le fichier :
*/
IriSP.Widgets.Timeline.prototype.keyPress = function(e) {
    var key = this.whichKey(e.which);
    var time = 0;
    
    //Entre 0 et 10, on met à jour la position du curseur dans la video.
    if(key > -1 && key < 11)
    {
        time = this.source.getDuration().getSeconds()/10*key;
        this.$timeline.slider("value",time);
        this.player.popcorn.currentTime(time);
        
        //On supprime le marqueur précédemment affiché.
        if(this.previousMarkerIdx > -1)
        {
            var previousMarker = IriSP.jQuery("#" + this.annotations[this.previousMarkerIdx].id.replace(":", "_"));
            this.hideMarkerBig(previousMarker);
        }
    }
    
    //p ou P pour mettre en pause.
    if(key == 21)
    {
        if(!this.paused)
        {
            this.paused = true;
            this.player.popcorn.pause();
        }
        else
        {
            this.paused = false;
            this.player.popcorn.play();
        }
    }
}

/*
 * Donne une clé correspondante à une touche donnée.
 * Est appelé dans le fichier :
 * Timeline > fonction keyPress.
*/
IriSP.Widgets.Timeline.prototype.whichKey = function(code) {
    var key;
    
    if(code > 47 && code < 58)
    {
        return (code - 48);
    }
    
    if(code == 115 || code == 83)
    {
        return 11;
    }
    
    //p ou P pour mettre la vidéo en pause.
    if(code == 112 || code == 80)
    {
        return 21;
    }
    
    switch(code)
    {
        case 224:
            key = 0;
        break;
        case 38:
            key = 1;
        break;
        case 233:
            key = 2;
        break;
        case 34:
            key = 3;
        break;
        case 39:
            key = 4;
        break;
        case 40:
            key = 5;
        break;
        case 45:
            key = 6;
        break;
        case 232:
            key = 7;
        break;
        case 95:
            key = 8;
        break;
        case 231:
            key = 9;
        break;
        default:
            key = -1;
    }
    
    return key;
}

/*
 * Fonction de sélection de la timeline.
 * Est appelé dans le fichier :
 * pointers > fonction pointersTimelineSelection.
*/
IriSP.Widgets.Timeline.prototype.selectTimeline = function() {
    //On crée les bordures.
    this.timelineSelected = true;
    this.$timelineBorderUp = "<div class='TL_Borders' id='TL_BorderUp'></div>";
    this.$timelineBorderDown = "<div class='TL_Borders' id='TL_BorderDown'></div>";
    this.$timelineBorderLeft = "<div class='TL_Borders' id='TL_BorderLeft'></div>";
    this.$timelineBorderRight = "<div class='TL_Borders' id='TL_BorderRight'></div>";
    //Les flèches verticales aussi.
    this.$arrowUp = "<div class='TL_Arrows' id='TL_ArrowUp'></div>";
    this.$arrowDown = "<div class='TL_Arrows' id='TL_ArrowDown'></div>";
    //On les ajoute.
    this.$.append(this.$timelineBorderUp + this.$timelineBorderDown + this.$timelineBorderLeft + this.$timelineBorderRight + this.$arrowUp + this.$arrowDown);
    //On calcule la position en Y de la timeline.
    var timelineTop = IriSP.jQuery("#LdtPlayer").position().top + IriSP.jQuery("#LdtPlayer").height();
    //On met les styles à jour.
    IriSP.jQuery("#TL_BorderUp").css(
    {
        "margin-top": -this.$timeline.height() - this.top_epsilon,
        left: this.$timeline.position().left,
        width: this.$timeline.width(),
        height: this.timelineBorderLength
    });
    IriSP.jQuery("#TL_BorderDown").css(
    {
        "margin-top": -this.timelineBorderLength - 2 - this.top_epsilon,
        left: this.$timeline.position().left,
        width: this.$timeline.width(),
        height: this.timelineBorderLength
    });
    IriSP.jQuery("#TL_BorderLeft").css(
    {
        "margin-top": -this.$timeline.height() - this.top_epsilon,
        left: this.$timeline.position().left,
        width: this.timelineBorderLength,
        height: this.$timeline.height()
    });
    IriSP.jQuery("#TL_BorderRight").css(
    {
        "margin-top": -this.$timeline.height() - this.top_epsilon,
        left: +this.$timeline.position().left + this.$timeline.width() - this.timelineBorderLength - 2,
        width: this.timelineBorderLength,
        height: this.$timeline.height()
    });
    
    IriSP.jQuery("#TL_ArrowUp").css(
    {
        "background-image": "url(" + this.imgDir + "arrow_up.png)",
        "margin-top": -this.$timeline.height() - IriSP.jQuery("#TL_ArrowUp").height() - this.top_epsilon,
        left: this.$timeline.position().left - IriSP.jQuery("#TL_ArrowUp").width()/2,
    });
    IriSP.jQuery("#TL_ArrowDown").css(
    {
        "background-image": "url(" + this.imgDir + "arrow_down.png)",
        "margin-top": -this.timelineBorderLength + this.timelineBorderLength - this.top_epsilon,
        left: this.$timeline.position().left - IriSP.jQuery("#TL_ArrowUp").width()/2,
    });
    
    IriSP.jQuery(".Ldt-Timeline .ui-slider-range").css("background-image", "url(" + this.imgDir + "past_timeline.png)");
}

/*
 * Déselectionne la timeline.
 * Est appelé dans le fichier :
 * playerControl > fonction exitTimeline.
*/
IriSP.Widgets.Timeline.prototype.deselectTimeline = function() {
    //On supprime les éléments qui faisaient que la timeline était sélectionnée.
    this.timelineSelected = false;
    IriSP.jQuery(".TL_Borders").remove();
    IriSP.jQuery(".TL_Arrows").remove();
    IriSP.jQuery(".Ldt-Timeline .ui-slider-range").css("background-image", "url(" + this.imgDir + "selected_timeline.png)");
}

/*
 * Se met à jour durant la lecture d'une video.
 * Est appelé dans le fichier :
 * Timeline
*/
IriSP.Widgets.Timeline.prototype.onTimeupdate = function() {
    //On récupère la position du curseur en secondes.
    var _time = this.player.popcorn.currentTime();
    //Position des flèches au cas où la timeline serait sélectionnée.
    var arrowLeft = Math.floor(+this.scaleIntervals(0, this.source.getDuration().getSeconds(), 0, this.$timeline.width(), _time) + this.$timeline.position().left) - IriSP.jQuery("#TL_ArrowUp").width()/2 + "px";
    //On met à jour la position du slider en ms.
    this.$timeline.slider("value",_time*1000);
    //On affiche les flèches si la timeline est sélectionnée.
    IriSP.jQuery(".TL_Arrows").css("display", "block");
    IriSP.jQuery("#TL_ArrowUp").css("left", arrowLeft);
    IriSP.jQuery("#TL_ArrowDown").css("left", arrowLeft);
    
    //Si on a une distance de 500 ms à un marqueur, on l'affiche.
    var nearestMarkerIdx = 0;
    for(var i = 0 ; i < this.annotations.length ; i++)
    {
        //S'il existe des marqueurs dans l'intervalle de temps actuel (ici 500ms).
        if(Math.abs(_time*1000 - this.annotations[i].begin.milliseconds) <= 250)
        {
            //On sélectionne le plus proche marqueur (dans les cas où il en existe plusieurs dans l'intervalle des 1s) ou bien le premier marqueur.
            if(Math.abs(_time*1000 - this.annotations[i].begin.milliseconds) < Math.abs(_time*1000 - this.annotations[nearestMarkerIdx].begin.milliseconds) || i == nearestMarkerIdx)
            {
                //Si le prochain marqueur se situe après le curseur de lecture, on passe donc au marqueur le plus proche.
                if(_time*1000 < this.annotations[i].begin.milliseconds)
                {
                    nearestMarkerIdx = i;
                    //S'il y a un changement de marqueur (marqueur actuel différent du précédent).
                    if(nearestMarkerIdx != this.previousMarkerIdx)
                    {
                        var currentMarker = IriSP.jQuery("#" + this.annotations[nearestMarkerIdx].id.replace(":", "_"));
                        //S'il existe un marqueur précédent, on le cache.
                        if(this.previousMarkerIdx > -1)
                        {
                            var previousMarker = IriSP.jQuery("#" + this.annotations[this.previousMarkerIdx].id.replace(":", "_"));
                            this.hideMarkerBig(previousMarker);
                        }
                        
                        this.showMarkerBig(currentMarker, this.annotations[nearestMarkerIdx].annotationType.contents.title);
                        //Mise à jour du marqueur précédent s'il y a un changement.
                        this.previousMarkerIdx = nearestMarkerIdx;
                    }
                }
            }
            this.currentMarkerIdx = nearestMarkerIdx;
        }
    }
}

/*
 * Met à jour l'affichage régulièrement.
 * Est appelé dans le fichier :
 * Timeline
*/
IriSP.Widgets.Timeline.prototype.timeDisplayUpdater = function() {
    //On récupère la position du curseur en secondes.
    var _time = this.player.popcorn.currentTime();
    //Position des flèches au cas où la timeline serait sélectionnée.
    var arrowLeft = Math.floor(+this.scaleIntervals(0, this.source.getDuration().getSeconds(), 0, this.$timeline.width(), _time) + this.$timeline.position().left) -
    this.$timeline.slider("value",_time*1000);
    //On affiche les flèches si la timeline est sélectionnée.
    IriSP.jQuery(".TL_Arrows").css("display", "block");
    IriSP.jQuery("#TL_ArrowUp").css("left", arrowLeft);
    IriSP.jQuery("#TL_ArrowDown").css("left", arrowLeft);
}

/*
 * Fonction appelée quand la souris est au dessus de la timeline.
*/
IriSP.Widgets.Timeline.prototype.onMouseover = function() {}

/*
 * Fonction appelée quand la souris est hors de la timeline.
*/
IriSP.Widgets.Timeline.prototype.onMouseout = function() {}

/*
 * Fonction appelée pour modifier la hauteur de la timeline.
*/
IriSP.Widgets.Timeline.prototype.animateToHeight = function(_height) {}

/*
 * Calcule le css de la timeline.
 * Est appelé dans le fichier :
 * Timeline > fonction draw.
*/
IriSP.Widgets.Timeline.prototype.calculateTimelineCss = function(_size) {
    //Longueur du player.
    var middleWidth = this.player.config.gui.width;
    //On met à jour la marge, les coordonnées et positions de la timeline.
    return {
        position: "absolute",
        top: "0px",
        left: "0px",
        width: middleWidth + "px",
        height: _size + "px",
        "margin-top": (-this.minimized_height - this.top_epsilon) + "px",
        "z-align": "50"
    };
}

/*
 * Calcule le css de la barre grise de milieu de la timeline.
 * Est appelé dans le fichier :
 * Timeline > fonction draw.
*/
IriSP.Widgets.Timeline.prototype.calculateTimelineMiddleCss = function(_size, _middleSize) {
    //Longueur du player.
    var middleWidth = this.player.config.gui.width;
    //On met à jour la marge, les coordonnées et positions de la barre grise de milieu de la timeline.
    return {
        position: "absolute",
        top: "0px",
        left: "0px",
        width: middleWidth + "px",
        height: _middleSize + "px",
        "margin-top": (-this.minimized_height/2 - _middleSize/2 - this.top_epsilon) + "px",
        "z-align": "100"
    };
}

/*
 * Calcule le css de la portion de la timeline qui a déjà été lue.
 * Est appelé dans le fichier :
 * Timeline > fonction draw.
*/
IriSP.Widgets.Timeline.prototype.calculateHandleCss = function(_size) {
    return {
        position: "absolute",
        top: "0px",
        left: "0px",
        height: (2 + _size) + "px",
        width: (2 + _size) + "px",
        "margin-left": -Math.ceil(2 + _size / 2) + "px",
        "z-align": "60"
    }
}

/*
 * Affiche les marqueurs lorsqu'ils sont affichés au passage du curseur.
 * Est appelé dans le fichier :
 * Timeline > fonction onTimeupdate.
*/
IriSP.Widgets.Timeline.prototype.showMarkerBig = function(marker, type) {
    //Si le marqueur est déjà affiché, on part.
    if(this.markerBigShown)
    {
        return;
    }
    
    //On annule le masquage du pointeur.
    clearTimeout(this.hideTimeout);
    
    var _this = this;
    //On met à jour la position du marqueur.
    var markerTop, markerLeft;
    
    if(marker.position() == null)
    {
        markerTop = 0;
        markerLeft = 0;
    }
    else
    {
        markerTop = marker.position().top;
        markerLeft = marker.position().left;
    }
    //Ses dimensions aussi.
    var markerWidth = marker.width(), markerHeight = marker.height();
    //On spécifie qu'il est affiché.
    this.markerBigShown = true;
    //On le crée.
    var markerBig = "<div class='TL_MarkersBig' id='MB_Text'>" + this.gesturesText[IriSP.jQuery.inArray(type, this.gestures)] + "<div class='TL_MarkersBig' id='MB_Spike'></div></div><div class='TL_MarkersBig' id='MB_Pic'></div>";
    //On l'ajoute.
    this.$.append(markerBig);
    //On forme ses éléments.
    var markerBigText = IriSP.jQuery("#MB_Text");
    var markerBigSpike = IriSP.jQuery("#MB_Spike");
    var markerBigPic = IriSP.jQuery("#MB_Pic");
    //On spécifie leurs coordonnées et dimensions.
    var markerBigTextWidth = markerBigText.outerWidth(), markerBigTextHeight = markerBigText.outerHeight();
    var markerBigSpikeWidth = markerBigSpike.width(), markerBigSpikeHeight = markerBigSpike.height();
    var markerBigPicWidth = markerBigPic.width(), markerBigPicHeight = markerBigPic.height();
    var markerBigPicTop = +parseFloat(marker.css("margin-top")) + markerHeight, markerBigPicLeft = (markerLeft - markerBigPicWidth/2 + markerWidth/2);
    var markerBigTextTop = (parseFloat(marker.css("margin-top")) - markerBigTextHeight - markerBigSpikeHeight), markerBigTextLeft = (markerLeft - (markerBigTextWidth - markerBigSpikeWidth)/2);
    var markerBigSpikeLeft = ((markerBigTextWidth - markerBigSpikeWidth)/2);
    //On va chercher les images correspondantes.
    marker.css("background-image", "url(" + this.imgDir + "selected_marker.png)");
    //On met à jour leur apparence.
    IriSP.jQuery("#MB_Text").css(
    {
        top: markerBigTextTop,
        left: markerBigTextLeft
    });
    IriSP.jQuery("#MB_Spike").css(
    {
        left: markerBigSpikeLeft
    });
    IriSP.jQuery("#MB_Pic").css(
    {
        "background-image": "url(" + this.markersDir + type + ".png)",
        top: markerBigPicTop,
        left: markerBigPicLeft,
        "z-index": "400"
    });
    //On l'affiche.
    IriSP.jQuery(".TL_MarkersBig").fadeTo(this.markerShowTime, "1");
    
    //On rajoute un timeout pour supprimer le marqueur après un certain temps.
    this.hideTimeout = setTimeout(function()
    {
        _this.hideMarkerBig(marker);
    }, this.markerLastTime);
}

/*
 * Cache un marqueur.
 * Est appelé dans le fichier :
 * Timeline > fonctions draw, keyPress, onTimeupdate et showMarkerBig.
*/
IriSP.Widgets.Timeline.prototype.hideMarkerBig = function(marker) {
    //S'il n'est pas affiché, on part.
    if(!this.markerBigShown)
    {
        return;
    }
    
    //On lui remet son apparence initiale.
    this.currentMarker = -1;
    this.markerBigShown = false;
    marker.css("background-image", "url(" + this.imgDir + "marker.png)");
    //On efface ce qui était affiché du marqueur.
    IriSP.jQuery(".TL_MarkersBig").fadeOut(this.markerShowTime).remove();
}

/*
 * Affiche le bas des marqueurs correspondants à la recherche.
 * Est appelé dans le fichier :
 * Timeline > fonction hideMarkersSearch.
*/
IriSP.Widgets.Timeline.prototype.showMarkersSearchByType = function(type) {
    //Si on est en mode SEARCH.
    if(this.currentMode != "SEARCH")
    {
        return;
    }
    
    var _this = this;
    
    //On récupère les annotations.
    var markersSearch = "", markersPicSearch = "";
    //Pour chaque annotation, on ajoute un double.
    for(var i = 0 ; i < this.annotations.length ; i++)
    {
        //Si elle correspond à la recherche.
        if(this.annotations[i].annotationType.contents.title == type)
        {
            //On récupère le marqueur associé à l'annotation.
            var markerId = this.annotations[i].id.replace(":", "_");
            
            markersSearch += "<div class='search_Marker' id='search_Marker_" + markerId + "'></div>";
            markersPicSearch += "<div class='search_MBPic' id='search_Pic_" + markerId + "'></div>";
        }
    }
    
    this.$.append(markersSearch + markersPicSearch);
    
    //On place chaque double.
    for(var i = 0 ; i < this.annotations.length ; i++)
    {
        //Si elle correspond à la recherche.
        if(this.annotations[i].annotationType.contents.title == type)
        {
            //On calcule les coordonnées et dimensions du marqueur.
            var markerId = this.annotations[i].id.replace(":", "_"), marker = IriSP.jQuery("#" + markerId);
            var markerTop = marker.position().top, markerLeft = marker.position().left, markerWidth = marker.width(), markerHeight = marker.height();
            var markerBigPicWidth = parseFloat(IriSP.jQuery(".search_MBPic").css("width")), markerBigPicHeight = parseFloat(IriSP.jQuery(".search_MBPic").css("height")), markerBigPicTop = +parseFloat(marker.css("margin-top")) + markerHeight, markerBigPicLeft = (markerLeft - markerBigPicWidth/2 + markerWidth/2);
            //On calcule son apparence et on le fait apparaître.
            IriSP.jQuery("#search_Pic_" + markerId).css(
            {
                position: "absolute",
                "background-image": "url(" + this.markersDir + type + ".png)",
                top: markerBigPicTop,
                left: markerBigPicLeft,
                "z-index": "300"
            }).fadeTo(this.markerShowTime, "1");
            
            IriSP.jQuery("#search_Marker_" + markerId).css(
            {
                position: "absolute",
                top: _this.$timelineMiddle.position().top - _this.top_epsilon,
                "margin-top": (-_this.$timeline.height()/2 - markerHeight/2),
                "background-image": "url(" + this.imgDir + "selected_marker.png)",
                left: markerLeft,
                "z-index": "300"
            }).fadeTo(this.markerShowTime, "1");
        }
    }
}

/*
 * Enlever une recherche faite précédemment.
 * Est appelé dans le fichier :
 * Timeline > fonctions searchByGesture et removeSearchByGesture.
*/
IriSP.Widgets.Timeline.prototype.hideMarkersSearch = function(type) {
    //Si on est en mode SEARCH.
    if(this.currentMode != "SEARCH")
    {
        return;
    }
    
    var _this = this;
    
    //On efface tous les marqueurs affichés.
    IriSP.jQuery(".search_MBPic").fadeOut(this.markerShowTime, function()
    {
        IriSP.jQuery("div").remove(".search_MBPic");
    });
    IriSP.jQuery(".search_Marker").fadeOut(this.markerShowTime, function()
    {
        IriSP.jQuery("div").remove(".search_Marker");
        
        //Si le type est définit, c'est qu'on souhaite remplacer cette recherche par une autre.
        if(type == undefined)
        {
            _this.currentMode = "VIDEO";
        }
        else
        {
            _this.showMarkersSearchByType(type);
            return;
        }
    });
    
    //Si à la base il n'y avait pas de marqueurs affichés, on crée une nouvelle recherche si le type est définit.
    if(IriSP.jQuery(".search_Marker").length == 0 && type != undefined)
    {
        this.showMarkersSearchByType(type);
    }
}

/*
 * Libère le player.
 * Est appelé dans les fichiers :
 * neighbours > fonction moveToNeighbour.
 * zoomInteractions > fonction unzoom.
*/
IriSP.Widgets.Timeline.prototype.freePlayer = function()
{
    IriSP.jQuery('body').unbind('keypress');
    IriSP.jQuery('.notifications').remove();
}

/*
 * Va au marqueur suivant/précédant lors d'un swipe right/left dans une lecture simple.
 * Prend comme argument le fait qu'il s'agisse d'un swipe left ou non (en prenant en condition toujours vraie 
 * que la fonction est appelée si et seulement si il y a swipe et que l'utilisateur ne tente pas d'aller vers un voisin.
 * Est appelé dans le fichier :
 * mosaic > fonction manageControlEvents.
*/
IriSP.Widgets.Timeline.prototype.switchToMarker = function(isSwipeLeft, searchedGesture)
{
   //On prend le temps actuel du curseur en ms.
    var currentCursorPosition = this.player.popcorn.currentTime() * 1000;
    //Position visée.
    var targetCursorPosition = currentCursorPosition;
    //Distance minimum de l'annotation par rapport au curseur et son index, ainsi que l'index - 1 pour le cas du swipe right.
    var minDistance = this.source.getDuration().milliseconds, minIdx = 0, mindIdx_1 = 0;
	
	//Condition de sélection du marqueur selon le type de swipe.
	var swipeCondition;
	
    //Si il y a au moins 1 annotation.
    if(this.annotations && this.annotations.length > 0)
    {
		//Pour toutes les annotations, on prend celle qui est la plus proche et supérieure à la position.
		for(var i = 0 ; i < this.annotations.length ; i++)
		{
			swipeCondition = (isSwipeLeft ? (currentCursorPosition < this.annotations[i].begin && minDistance > Math.abs(currentCursorPosition - this.annotations[i].begin)) : (currentCursorPosition > this.annotations[i].begin && minDistance > Math.abs(currentCursorPosition - this.annotations[i].begin)));
			
			if(swipeCondition)
			{
				//Si on recherche une gesture.
				if(searchedGesture != '')
				{
					//Si l'annotation actuelle ne correspond pas à la gesture recherchée, on passe.
					if(this.annotations[i].annotationType.contents.title != searchedGesture)
					{
						continue;
					}
				}
				
				//On calcule la plus petite distance entre le marqueur marqueur actuel et les autres.
				minDistance = (currentCursorPosition - this.annotations[i].begin);
				minIdx = i;
			}
		}
        
        //On obtient la position du plus proche marqueur.
        targetCursorPosition = this.annotations[minIdx].begin;
        
        //Si le marqueur est situé minimum à 1s du début de la video, on place la position cible à 1s avant celle du marqueur recherché.
        if(this.annotations[minIdx].begin > 1000)
        {
            targetCursorPosition -= 1000;
        }
    }
    
    //On place le marqueur au niveau de la position cible.
    this.player.popcorn.currentTime(targetCursorPosition / 1000);
}

/*
 * Indique s'il y a des marqueurs devant le curseur (pour une recherche).
 * Est appelé dans le fichier :
 * mosaic > fonction manageControlEvents.
*/
IriSP.Widgets.Timeline.prototype.isAMarkerAhead = function(searchedGesture)
{
    if(searchedGesture == '')
    {
        return true;
    }
    
    //On prend le temps actuel du curseur en ms.
    var currentCursorPosition = this.player.popcorn.currentTime() * 1000;
    //Position visée.
    var targetCursorPosition = currentCursorPosition;
    //Distance minimum de l'annotation par rapport au curseur et son index, ainsi que l'index - 1 pour le cas du swipe right.
    var minDistance = this.source.getDuration().milliseconds, minIdx = 0, mindIdx_1 = 0;
    
    //Si il y a au moins 1 annotation.
    if(this.annotations && this.annotations.length > 0)
    {
        //Pour toutes les annotations, on prend celle qui est la plus proche et supérieure à la position.
        for(var i = 0 ; i < this.annotations.length ; i++)
        {
            if(this.annotations[i].annotationType.contents.title == searchedGesture && currentCursorPosition < this.annotations[i].begin)
            {
                return true;
            }
        }
    }
    
    //Si elle n'a pas été trouvée on renvoie faux.
    return false;
}

/*
 * Quand on entre dans la vidéo après un filtrage, on va au premier marqueur correspondant à la recherche (à l'exception d'une recherche infructueuse).
 * Est appelé dans les fichiers :
 * mosaic > fonctions onMouseUp et onMarkersReady.
 * curvesDetector > fonction updateDists.
*/
IriSP.Widgets.Timeline.prototype.goToFirstSearchedMarker = function(gesture)
{
    if(_.include(this.gestures, gesture))
    {
        if(this.annotations && this.annotations.length > 0)
        {
            var minIdx = 0, minPosition = this.source.getDuration().milliseconds, targetCursorPosition = 0;
            
            //On parcourt les annotations, pour chaque correspondant à la gesture recherchée, on trouve celle qui se trouve à la position minimum.
            for(var i = 0 ; i < this.annotations.length ; i++)
            {
                //Si le marker n'est pas du type recherché, on passe.
                if(this.annotations[i].annotationType.contents.title != gesture)
                {
                    continue;
                }
                else if(minPosition > this.annotations[i].begin)
                {
                    minPosition = this.annotations[i].begin;
                    minIdx = i;
                }
            }
            
            targetCursorPosition = this.annotations[minIdx].begin;
            
            //Si le marqueur est situé minimum à 1s du début de la video, on place la position cible à 1s avant celle du marqueur recherché.
            if(this.annotations[minIdx].begin > 1000)
            {
                targetCursorPosition -= 1000;
            }
            
            //On place le marqueur au niveau de la position cible.
            this.player.popcorn.currentTime(targetCursorPosition / 1000);
        }
    }
}

/*
 * Renvoie vrai si il y a au moins une gesture de notre recherche dans les marqueurs de la video.
 * Est appelé dans les fichiers :
 * mosaic > fonctions onMouseUp et onMarkersReady.
 * curvesDetector > fonction updateDists.
*/
IriSP.Widgets.Timeline.prototype.atLeastOneSearchMarker = function(gesture)
{
    if(_.include(this.gestures, gesture))
    {
        if(this.annotations && this.annotations.length > 0)
        {
            //On parcourt les annotations, pour chaque correspondant à la gesture recherchée, on trouve celle qui se trouve à la position minimum.
            for(var i = 0 ; i < this.annotations.length ; i++)
            {
                //Si le marker est reconnu, c'est bon.
                if(this.annotations[i].annotationType.contents.title == gesture)
                {
                    return true;
                }
            }
            
            return false;
        }
    }
}