front_idill/src/mosaic/js/mosaic.js
author bastiena
Mon, 09 Jul 2012 14:24:42 +0200
changeset 47 4e1ee94d70b1
parent 46 9d7a2aa112fb
child 52 277c94533395
permissions -rw-r--r--
Front IDILL: Help adapted to screen size & mouse interactions

/*
* 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 : mosaic.js
 * 
 * Auteur : alexandre.bastien@iri.centrepompidou.fr
 * 
 * Fonctionnalités : Définit la "classe" mosaïque et définit des fonctions d'intéractions.
 */

/*
 * Classe définissant la mosaïque.
 * Elle contient sa longueur, le nombre d'images total, une liste d'urls pour les vidéos, leurs snapshots principaux et leur position.
 * Contient également les dimensions en px de la mosaïque.
 */
function mosaic(config, default_conf)
{
	//Interactions souris/kinect.
	this.mouseInteractions = true;
	//Interactions avec/sans préphase.
	this.prephaseEnabled = true;
	
	this.gestures = ["fall", "jump", "circle", "screw", "bend", "arc", "pendulum", "knee-up", "right-angle", "wave", "slow", "hello", "no-motion", "wheel", "contact", "run", "up-down", "grand-jete"];

	//Chemin du fichier de configuration.
	this.config_path = config;
	//Configuration par défaut en cas de valeur erronnée.
	this.default_config = default_conf;
	this.config = new Object();
	//Tableaux des urls des vidéos, des snapshots et de leur position dans la mosaïque.
	this.videos = [];
	this.urls = [];
	this.sources = [];
	this.imgs = [];
	this.opacities = [];
	this.timeToGoAt = [];
	this.ids = [];
	this.fillingIds = [];
	this.currentRandomVideoIdx = 0;
	
	//Dictionnaire pour les courbes de recherche.
	this.dictionary = [];
	
	//Dernières positions des pointeurs.
	this.mainPointerLastX;
	this.mainPointerLastY;
	this.secondPointerLastX;
	this.secondPointerLastY;
	//Dernières positions avant le lancement de la fonction d'idle.
	this.mainPointerIdleStartX;
	this.mainPointerIdleStartY;
	this.secondPointerIdleStartX;
	this.secondPointerIdleStartY;
	
	//Coordonnées de la souris dans le mode d'interaction souris.
	this.mousePosX;
	this.mousePosY;
	//Coordonnées précédentes de la souris dans le mode d'interaction souris.
	this.mousePosLastX;
	this.mousePosLastY;
	//Valeur du déplacement entre un mouse up et un mouse down.
	this.mouseUpDownDelta = 0;
	
	//Dimensions de la mosaïque en pixels.
	this.width;
	this.height;
	//Dimensions d'un snapshot en pixels.
	this.snapshotWidth;
	this.snapshotHeight;
	//Espacement entre les snapshots en pixels.
	this.marginWidth;
	
	//Booléens permettant ou non certaines intéractions selon le contexte.
	this.zoomed = false;
	this.fullscreen = false;
	this.canMoveToNeighbour = false;
	this.mainPointerExitBorder = false;
	this.secondPointerExitBorder = false;
	this.isMainPointerDisplayed = false;
	this.isSecondPointerDisplayed = false;
	this.helpDisplayed = false;
	//Indique si l'utilisateur a manuellement pausé la vidéo.
	this.userPaused = false;
	//Indique si on est en train de se déplacer vers un voisin.
	this.currentlyMoving = false;
	//Indique si on est en train de dézoomer vers la mosaïque.
	this.currentlyUnzooming = false;
	//Indique si on peut s'approcher de kinect.
	this.canStart = false;
	//Indique si on est actuellement sur un snapshot.
	this.isOnASnapshot = false;
	//Indique si l'idle des pointeurs est disponible (deux mains détectées).
	this.pointersIdleAvailable = false;
	//Indique si le timeout pour l'idle des pointeurs à besoin d'être lancé.
	this.pointersIdleNeedLaunch = false;
	//Indique si les deux mains sont là.
	this.areBothPointersHere = false;
	//Indique si le timeout pour la détection de deux pointeurs a été lancé.
	this.areBothPointersTimeoutLaunched = false;
	//Indique si la mosaïque a été filtrée.
	this.isMosaicFiltered = false;
	//Indique si on est actuellement dans une recherche par gesture.
	this.isCurrentlyInASearchByGesture = false;
	//Indique si un pointeur est déjà sur une notification de recherche par gesture.
	this.alreadyOnNotification = false;
	//Si on a fait un swipe.
	this.isSwipe = false;
	//On peut swiper.
	this.canSwipe = false;
	//On passe vers une autre video automatiquement à la fin d'une lecture.
	this.autoMove = false;
	//Si l'utilisateur a demandé à sélectionner la TL.
	this.isTLRequested = false;
	//Le pointeur gauche a sélectionné la TL.
	this.isTLSelectedBySecondPointer = false;
	//Le pointeur droit a sélectionné la TL.
	this.isTLSelectedByMainPointer = false;
	//On peut afficher l'aide.
	this.canNotifyHelp = false;
	//Indique si la mosaique est en train d'être filtrée.
	this.isMosaicFiltering = false;
	this.arrowLeftLoading = false;
	this.arrowRightLoading = false;
	this.arrowUpLoading = false;
	this.arrowDownLoading = false;
	//On est dans une recherche par courbes.
	this.isSearchByCurvesOn = false;
	this.canDrawNextCurve = false;
	//Dans le mode d'interaction souris, indique si on se situe actuellement sur un snapshot entièrement prézoomé.
	this.isOnAPrezoomSN = false;
	//Indique si une courbe de recherche donne au moins un résultat.
	this.curvesGesturesFound = false;
	//Indique si on souhaite supprimer la recherche en cours.
	this.gestureDelRequested = false;
	//Code de gesture actuellement calculé par les détecteurs de courbes.
	this.actualCode = '';
	//Indique si l'utilisateur est entré dans la zone de recherche.
	this.isUserInSearchZone = false;
	
	//Timeout (attente) pour le zoom après un préZoom.
	this.zoomTimeout = null;
	//Timeout (attente) pour le passage vers un voisin.
	this.moveToNeighbourTimeout = null;
	this.mainPointerExitBorderTimeout = null;
	this.secondPointerExitBorderTimeout = null;
	//Idle time pour les pointeurs afin d'informer le front qu'on souhaite faire une recherche.
	this.pointersSearchIdleTimeout = null;
	//Vérifie toutes les N ms que les deux pointeurs sont détectés.
	this.areBothPointersHereTimeout = null;
	//Délai de suppression d'une notification de recherche par gesture.
	this.removeNotificationByGestureTimeout = null;
	//Délai de suppression d'une notification de recherche par gesture infructueuse.
	this.removeFailedNotificationByGestureTimeout = null;
	//Délai avant la suppression de notification swipe.
	this.notifySwipeTimeout = null;
	//Délai pour la sélection de la TL.
	this.selectTLTimeout = null;
	//Délai pour slider sur la TL.
	this.canSlideInTLTimeout = null;
	//Délai pour afficher l'aide.
	this.canNotifyHelpTimeout = null;
	this.arrowLeftTimeout = null;
	this.arrowRightTimeout = null;
	this.arrowUpTimeout = null;
	this.arrowDownTimeout = null;
	
	this.arrowSpinnerTimeout = null;
	this.nouserTimeout = null;
	this.nextDrawCurveTimeout = null;
	
	//Dernier message INCOMING (pour éviter d'effectuer n fois la même action.
	this.lastIncomingMessage = '';
	
	//Type de marqueur recherché dans la mosaïque (en mode filter).
	this.filterSearchedType = "";
	
	//Mode actuel.
	this.currentMode = "NO-USER";
	//Snapshot sur lequel on a zoomé.
	this.previousZoomedSN = null;
	//Snapshot sur lequel on a prezoomé.
	this.previousPrezoomDiv = null;
	//Son ID.
	this.previousId = null;
	//Dernier snapshot prézoomé non null.
	this.lastNonNullSN = null;
	//Largeur de la marge pour le centrage vertical de la mosaïque.
	this.MPTop_margin;
	this.top_margin;
	
	//Gestures actuellement cherchées dans les vidéos.
	this.currentSearchGesture = [];
	
	//Mosaïque locale.
	this.localMos;
	//Position des voisins lors d'un zoom.
	this.neighboursIds = [];
	//ID du snapshot du milieu lors d'un zoom.
	this.centerId;
	
	//Voisins sélectionnés par les pointeurs.
	this.mainPointerNeighbourSelectedId = null;
	this.secondPointerNeighbourSelectedId = null;
	
	//Snapshots a afficher.
	this.snapshotsToShow = 1;
	
	//Lecteur.
	this.player = null;
	//Si le lecteur est prêt.
	this.playerIsReady = false;
	
	//Annotations (pour les marqueurs los d'un filtrage).
	this.annotations = [];
	
	//Client websocket pour recevoir les notifications du Middleware.
	this.client;
	
	//Coordonnées et dimensions d'un snapshot zoomé.
	this.snTop = 0;
	this.snLeft = 0;
	this.snWidth = 0;
	this.snHeight = 0;
	
	this.searchCanvas;
	//Position actuelle de la vidéo zoomée.
	this.notifyTopVideo;
	this.notifyLeftVideo;
	this.loadParameters(this.config_path);
}

/*
 * Méthode d'affichage de la mosaïque.
 * Génère une matrice de imgs.
 */
mosaic.prototype.createMosaic = function()
{
	// console.log('CREATE');
	// this.currentMode = "NO-USER";
	var initPanel = '<div id="initPanel"></div>';
	var mp = $('#mainPanel');
	mp.append(initPanel);
	$('#initPanel').css(
	{
		background: 'transparent',
		width: mp.width(),
		height: mp.height(),
		top: mp.position().top,
		left: mp.position().left,
		'margin-top': this.MPTop_margin
	});
	
	var len = this.config['length'], imgs = this.config['imagesToShow'], imgsTotal = this.config['imagesTotal'];
	
	//S'il s'agit d'un rectangle.
    if(imgs % len == 0)
    {
		this.lastIncomingMessage = 'INCOMING-0';
		
		var str = '';
		
		if(this.imgs.length >= imgs)
		{
			for(var i = 0 ; i < imgs ; i++)
			{
				//On charge les images de petite taille pour ne pas surcharger la mosaïque lors de l'affichage global.
				str += '<div id="snapshotDiv-' + i + '" class="snapshotDivs" style="opacity: 0;"><img id="snapshot-' + i + '" class="snapshots" src="snapshots-little/' + this.imgs[i] + '" /></div>';
				//Au départ aucune vidéo n'a de gesture de recherche.
				this.currentSearchGesture[i] = '';
			}
		}
		
		return str + '<div id="ghostPanel"></div>';
	}
	else
	{
		alert("Le nombre d'images a afficher doit être divisible par la longueur de la mosaïque !");
	}
}

/*
 * Permet de raffraichir la mosaïque.
 */
mosaic.prototype.loadMosaic = function()
{
	// console.log('LOAD');
	var createMosaic = this.createMosaic();
	
	if(createMosaic == '')
	{
		return;
	}
	
	var _this = this;
	
    //On affecte les chemins vers les images à la mosaïque.
    this.previousZoomedSN;
    //this.width = 
	// console.log(createMosaic);
    //On met à jour la mosaïque.
    $('#mainPanel').html(createMosaic);
    //On récupère la taille des bordures.
    this.marginWidth = $('.snapshotDivs').css('margin-bottom');
    this.marginWidth = parseFloat(this.marginWidth)*2;
    //On calcule la taille des divs contenant les snapshots.
    this.width = $('#mainPanel').innerWidth();
    //On ne calculera pas tout de suite la hauteur de la mosaique étant donnée qu'elle est calculée par la suite dynamiquement.
    this.snapshotWidth = this.width / this.config['length'] - this.marginWidth;
    this.snapshotHeight = this.snapshotWidth*9/16;
    $('.snapshotDivs').css('width', this.snapshotWidth).css('height', this.snapshotHeight).css('margin', this.marginWidth/2);
    
    this.height = $('#mainPanel').innerHeight();
    //On centre verticalement la mosaïque.
    this.MPTop_margin = ($(document).height() - $('#mainPanel').height())/2;
    $('#mainPanel').css('margin-top', this.MPTop_margin).css('margin-bottom', this.MPTop_margin);
	
	//On fait coincider le background du body avec celui de la mosaïque.
	$('body').css('background-position', '0px ' + this.MPTop_margin + 'px');
	
	/*$('.snapshotDivs').mouseover(function ()
	{
		//On effectue un prézoom dès qu'on va sur une image.
		_this.preZoom($(this));
	});*/
	
	this.addPointers();
	
	//Si dans le metadata player _ n'est pas déclaré, on le déclare.
	if(typeof _ !== "undefined" && typeof IriSP._ === "undefined")
	{
		IriSP._ = _;
	}
	
	if(typeof $ !== "undefined" && typeof IriSP.jQuery === "undefined")
	{
		IriSP.jQuery = $;
	}
	
	//On charge les marqueurs.
	var sourceManager = new IriSP.Model.Directory(),
	globalAnnotations = new IriSP.Model.List(sourceManager),
	nbFichiers = _this.urls.length,
	fichiersCharges = 0;
	
	for (var i = 0; i < nbFichiers; i++)
	{
		// console.log('url : ' + _this.urls[i]);
		_this.sources[i] = sourceManager.remoteSource({url: _this.urls[i], serializer: IriSP.serializers.ldt});
		_this.sources[i].onLoad(function()
		{
			var source = this;
			// console.log(source);
			globalAnnotations.addElements(source.getAnnotations());
			// console.log(source.url + ' ' + source.getAnnotations().length);
			fichiersCharges++;
			if (fichiersCharges == nbFichiers)
			{
				// instructions à exécuter quand tout est chargé
				_this.annotations = globalAnnotations;
				// console.log(_this.annotations.length + ' ' + nbFichiers);
				console.log(_this.annotations.length + ' annotations loaded from ' + nbFichiers + ' files.');
				
				//Si on gère les interactions à la souris.
				if(_this.mouseInteractions)
				{
					$(window).mousemove(function(e)
					{
						_this.refreshMainPointer(e.pageX, e.pageY, _this);
						_this.mousePosX = e.pageX;
						_this.mousePosY = e.pageY;
					});
				}
				
				if(_this.prephaseEnabled && !_this.mouseInteractions)
				{
					_this.init();
					_this.showNImages(0);
					_this.currentMode = "NO-USER";
				}
				else
				{
					_this.showNImages(20);
					_this.currentMode = "MOSAIC";
				}
				
				// /!\ //
				// _this.currentMode = "FILTER";
				// _this.showNImages(20);
				//_this.isSearchByCurvesOn = true;
				// _this.startSearch();
				// console.log('CANVAS READY');
				// /!\ //
				
				_this.previousZoomedSN = $('#snapshotDiv-' + _this.fillingIds[0]);
			}
		});
	}
	
	if(this.mouseInteractions)
	{
		//Si on fait un mouse down sur le body, on vérifie enregistre le déplacement de la souris jusqu'au prochain mouse up.
		$(window).mousedown(function (e)
		{
			_this.removeSearchNotificationIfOnIt(e.pageX, e.pageY);
			
			_this.isUserInSearchZone = true;
			if(_this.isSearchByCurvesOn)
			{
				_this.searchCanvas.onPointerIn(_this.mousePosX, _this.mousePosY, null, null);
			}
			
			console.log('mdown');
			//On écoute le déplacement de la souris.
			$(window).mousemove(function(e)
			{
				if(_this.isSearchByCurvesOn)
				{
					_this.searchCanvas.onPointerMove(_this.mousePosX, _this.mousePosY - _this.MPTop_margin, null, null);
				}
				
				//On met à jour l'ancienne position de la souris si elle est nulle.
				if(!_this.mousePosLastX && _this.mousePosLastX != 0)
				{
					_this.mousePosLastX = _this.mousePosX;
				}
				if(!_this.mousePosLastY && _this.mousePosLastY != 0)
				{
					_this.mousePosLastY = _this.mousePosY;
				}
				
				//Le delta s'accroît si la souris bouge.
				_this.mouseUpDownDelta += Math.floor(Math.sqrt((_this.mousePosLastX - e.pageX) * (_this.mousePosLastX - e.pageX) + (_this.mousePosLastY - e.pageY) * (_this.mousePosLastY - e.pageY)));
				
				// console.log(_this.mouseUpDownDelta, _this.mousePosLastX, e.pageX);
				
				if(_this.mousePosLastX != _this.mousePosX)
				{
					_this.mousePosLastX = _this.mousePosX;
				}
				if(_this.mousePosLastY != _this.mousePosY)
				{
					_this.mousePosLastY = _this.mousePosY;
				}
				
				//Si la souris a parcouru une trop grande distance, on entre en recherche.
				if(_this.mouseUpDownDelta > _this.config['mouseUpDownDeltaTreshold'])
				{
					//Si on est en mosaique, on entre en filtrage.
					if(_this.currentMode == "MOSAIC")
					{
						_this.preUnzoom();
						_this.currentMode = "FILTER";
						_this.isMosaicFiltered = true;
						
						console.log(_this.date() + ' - ENTRE EN MODE FILTRAGE');
						
						_this.isSearchByCurvesOn = true;
						_this.startSearch();
						
						/*if(!_this.curvesGesturesFound)
						{
							this.removeNotifications();
							_this.filterSearch();
						}*/
						
						_this.searchCanvas.onPointerIn(_this.mousePosX, _this.mousePosY - _this.MPTop_margin, null, null);
					}
					else if(_this.currentMode == "FILTER" && !_this.isSearchByCurvesOn && _this.isUserInSearchZone)
					{
						console.log('after search');
						_this.preUnzoom();
						_this.isSearchByCurvesOn = true;
						_this.startSearch();
						_this.searchCanvas.onPointerIn(_this.mousePosX, _this.mousePosY - _this.MPTop_margin, null, null);
					}
					//Si on est dans une vidéo, on entre en recherche.
					else if(_this.currentMode == "VIDEO" || _this.currentMode == "TIMELINE")
					{
						_this.currentMode = "SEARCH";
						
						console.log(_this.date() + ' - ENTRE EN MODE RECHERCHE');
						
						_this.isSearchByCurvesOn = true;
						_this.startSearch();
						
						/*if(!_this.curvesGesturesFound)
						{
							this.removeNotifications();
							_this.searchSearch();
						}*/
						
						_this.searchCanvas.onPointerIn(_this.mousePosX, _this.mousePosY - _this.MPTop_margin, null, null);
					}
					else if(_this.currentMode == "SEARCH" && !_this.isSearchByCurvesOn)
					{
						_this.isSearchByCurvesOn = true;
						_this.startSearch();
						_this.searchCanvas.onPointerIn(_this.mousePosX, _this.mousePosY - _this.MPTop_margin, null, null);
					}
					
					//Il est possible d'afficher l'aide.
					if(!_this.canNotifyHelp)
					{
						_this.canNotifyHelpTimeout = setTimeout(function()
						{
							_this.canNotifyHelp = true;
						}, _this.config['timeoutCanNotifyHelp']);
					}
				}
			});
			
			//Si on fait un mouse up après ce mouse down.
			$(window).mouseup(function()
			{
				console.log('mup');
				
				if(_this.isSearchByCurvesOn)
				{
					_this.isUserInSearchZone = false;
					
					var gesture_match = _this.gestureWithSameCode(_this.actualCode);
					_this.actualCode = '';
					
					if(gesture_match.length > 0)
					{
						if(_this.currentMode == "SEARCH" && _this.playerIsReady)
						{
							_this.player.widgets[0].searchByGesture(gesture_match);
							_this.isCurrentlyInASearchByGesture = _this.player.widgets[0].isCurrentlyInASearchByGesture;
							
							if(_this.player && _this.player.widgets[0] && _this.timeToGoAt[_this.centerId] === 0 && _this.player.widgets[0].atLeastOneSearchMarker(_this.currentSearchGesture[_this.centerId]))
							{
								_this.player.widgets[0].goToFirstSearchedMarker(_this.currentSearchGesture[_this.centerId]);
							}
							
							_this.removeNotifications();
							_this.currentSearchGesture[_this.centerId] = gesture_match;
							_this.searchGesture(gesture_match, 'valid');
							_this.curvesGesturesFound = false;
						}
						else if(_this.currentMode == "FILTER")
						{
							if(_this.isMosaicFiltered)
							{
								_this.removeNotifications();
								_this.filterSearchedType = gesture_match;
								_this.filterGesture(gesture_match, 'valid');
								_this.searchFilter(gesture_match);
								_this.curvesGesturesFound = false;
							}
						}
					}
					
					_this.searchCanvas.onPointerOut();
				}
				
				//On unbind ce qui a été bindé après le mouse up.
				$(window).unbind('mousemove');
				$(window).unbind('mouseup');
				//On rebind le mousemove principal du body, car ils ont tous été unbindés.
				$(window).mousemove(function(e)
				{
					_this.refreshMainPointer(e.pageX, e.pageY, _this);
					_this.mousePosX = e.pageX;
					_this.mousePosY = e.pageY;
				});
				
				_this.mousePosLastX = null;
				_this.mousePosLastY = null;
				
				//Si la distance parcourue par la souris entre le mouse down et le mouse up est inférieure ou égale au seuil.
				if(_this.mouseUpDownDelta <= _this.config['mouseUpDownDeltaTreshold'])
				{
					//Si on est sur un snapshot prézoomé.
					if(_this.isOnAPrezoomSN && _this.previousZoomedSN != '' && (_this.currentMode == 'MOSAIC' || _this.currentMode == 'FILTER'))
					{
						_this.zoom();
					}
				}
				
				_this.mouseUpDownDelta = 0;
				_this.isSearchByCurvesOn = false;
				_this.leaveSearch();
				
				if(_this.currentMode == 'FILTER' && _this.filterSearchedType != '')
				{
					_this.removeNotifications();
					_this.filterGesture(_this.filterSearchedType, 'valid');
				}
			});
		});
	}
}

/*
 * Charge les paramètres du Front. Local (true/false) est le mode de chargement des données.
*/
mosaic.prototype.loadParameters = function(file_path)
{
	var _this = this;
	
	var supposedToBeInt = ['length', 'imagesToShow', 'totalImages', 'timePrezoom', 'timePreUnzoom', 'timeZoom', 'zoomTime', 'timeUnzoom', 'timeNeighbourGlowing', 'timeNeighbourUnglowing', 'timeMovingToNeighbour', 'timeSearchFade', 'timeNotifyFade', 'timeFilterFade', 'timeANFade', 'timeFilling', 'zoomedMargin', 'timeoutZoom', 'timeoutUnzoom', 'timeoutMoveToNeighbour', 'timeoutPointersIdle', 'timeoutAreBothPointersHere', 'timeoutRemoveNotificationByGesture', 'timeoutRemoveFailedNotificationByGesture', 'timeoutNotifySwipe', 'timeoutSelectTL', 'timeoutSlideTL', 'timeoutCanNotifyHelp', 'timeoutRemoveSpinner', 'timeoutNouser', 'timeoutNextDrawCurve', 'mouseUpDownDeltaTreshold'];
	var supposedToBeFloat = ['zoomPercentage', 'prezoomPercentage'];
	
	$.getJSON(file_path, function(data)
	{
		for(key in data)
		{
			var val = data[key];
			
			if(_.include(supposedToBeInt, key))
			{
				var intVal = parseInt(val);
				if(isNaN(intVal))
				{
					// console.log(_this.default_config);
					_this.config[key] = _this.default_config[key];
					console.log("param[" + key + "] : Valeur " + val + " incorrecte (non Int). Valeur par défaut " + _this.default_config[key] + " chargée à la place.");
				}
				else
				{
					_this.config[key] = intVal;
				}
			}
			else if(_.include(supposedToBeFloat, key))
			{
				var floatVal = parseFloat(val);
				if(isNaN(floatVal))
				{
					_this.config[key] = _this.default_config[key];
					console.log("param[" + key + "] : Valeur " + val + " incorrecte (non Float). Valeur par défaut " + _this.default_config[key] + " chargée à la place.");
				}
				else
				{
					_this.config[key] = floatVal;
				}
			}
			else
			{
				_this.config[key] = val;
			}
		}
		
		//On remplit le tableau d'ids.
		for(var i = 0 ; i < _this.config['totalImages'] ; i++)
			_this.ids.push(i);
		//On les mélange.
		_this.ids.sort(function()
		{
			return 0.5 - Math.random()
		});
		
		//On remplit le tableau d'ids destiné à afficher les snapshots au fur et à mesure.
		for(var i = 0 ; i < _this.config['imagesToShow'] ; i++)
			_this.fillingIds.push(i);
		//On les mélange.
		_this.fillingIds.sort(function()
		{
			return 0.5 - Math.random()
		});
		
		if(_this.config['local'] == 'true')
		{
			// console.log("Loading local metadata.");
			_this.loadFromJson('./player/json/local_videos.json');
		}
		else
		{
			// console.log("Loading online metadata.");
			_this.loadFromJson('./player/json/online_videos.json');
		}
		
		//On initialise le client.
		if(!_this.mouseInteractions)
		{
			_this.client = new client(_this.config['host'], _this.config['port'], _this);
		}
		
		_this.getDictionary();
		_this.getLang();
	});
}

/*
 * Phase principale (utilisateur non détecté). Une vidéo se lance aléatoirement.
 * L'interface est identique à celle du zoom, mais sans interaction possible avec les voisins, ni les controles timeline.
 * Lors de la fin d'une lecture, on dézoome vers la mosaïque, puis on rezoome vers un autre snapshot (aléatoire).
*/
mosaic.prototype.init = function()
{
	var _this = this;

	if(this.currentRandomVideoIdx > this.config['imagesToShow'])
	{
		this.currentRandomVideoIdx = 0;
	}
	
	
	this.previousZoomedSN = $('#snapshotDiv-' + this.fillingIds[this.currentRandomVideoIdx]);
	this.previousId = $('img', this.previousZoomedSN).attr('id');
	
	// console.log('CURRENT MODE : ' + _this.currentMode);
	// console.log('ids', this.fillingIds[this.currentRandomVideoIdx]);
	
	this.previousZoomedSN.fadeTo(this.config['timePrezoom'], 1, function()
	{
		// console.log('CURRENT MODE : ' + _this.currentMode);
		_this.zoom();
		_this.currentRandomVideoIdx++;
	});
}

/*
 * Remplissage de la mosaïque en fonction du nombre d'images à afficher.
*/
mosaic.prototype.showNImages = function(n)
{
	if(this.currentlyMoving)
	{
		return;
	}
	
	// console.log('INCOMING ----- ' + n);
	//Si il y a plus d'un snapshot à afficher, on entre dans le mode INCOMING avec en paramètre le nombre à afficher.
	if(n > 1 && n < this.config['imagesToShow'])
	{
		this.currentMode = "INCOMING-" + n;
		this.unzoom();
		
		for(var i = 0 ; i < this.config['imagesToShow'] ; i++)
		{
			this.currentSearchGesture[i] = '';
		}
		
		this.removeNotifications();
		this.isMosaicFiltered = false;
		this.isCurrentlyInASearchByGesture = false;
		$('#mainPointer').fadeTo(this.config['timePrezoom'], 0);
		$('#secondPointer').fadeTo(this.config['timePrezoom'], 0);
		$('#spinner').remove();
		this.deselectAllNeighbours();
		$('.prezoomContainers').remove();
	}
	// console.log('n : ' + n);
	if(n >= this.config['imagesToShow'])
	{
		// this.unzoom();
		if(this.currentMode == "NO-USER" || this.currentMode.indexOf("INCOMING-") > -1)
		{
			if(!this.mouseInteractions)
			{
				this.currentMode = "INCOMING-20";
				this.unzoom();
			}
			
			this.currentMode = "MOSAIC";
			this.removeNotifications();
			this.mosaicSelectionAndSearch();
			clearTimeout(this.nouserTimeout);
			console.log('OK');
		}
		//On affiche les notifications.
		// this.notifySelectionSearchMosaicFull();
	
		//$('#mainPointer').fadeTo(this.config['timePrezoom'], 1);
		//$('#secondPointer').fadeTo(this.config['timePrezoom'], 1);
	}
	
	//Pour les snapshots à afficher.
	for(var i = 0 ; i < n ; i++)
	{
		//Si les snapshots ne sont pas affichés.
		if($('#snapshotDiv-' + this.fillingIds[i]).css('opacity') < 1)
		{
			//On les fait apparaître.
			$('#snapshotDiv-' + this.fillingIds[i]).fadeTo(this.config['timeFilling'], '1');
		}
	}
	//Pour ceux à masquer.
	for(var i = n ; i < this.config['imagesToShow'] ; i++)
	{
		//Si les snapshots ne sont pas masqués et qu'il ne s'agit pas du dernier snapshot en lecture aléatoire (mode NO-USER).
		if($('#snapshotDiv-' + this.fillingIds[i]).css('opacity') > 0 && this.fillingIds[i] != this.currentRandomVideoIdx)
		{
			//On les masque.
			$('#snapshotDiv-' + this.fillingIds[i]).fadeTo(this.config['timeFilling'], '0');
		}
	}
}

/*
 * Gère les événements de contrôle dans la mosaïque.
*/
mosaic.prototype.manageControlEvents = function(event)
{
	// console.log('manage');
	
	var _this = this;
	
	if(typeof event === 'undefined')
	{
		return;
	}
	
	var gestureReceived = '';
	
	if(event.indexOf("INCOMING-") != -1 && this.prephaseEnabled)
	{
		// console.log(this.date() + ' ' + event);
		// console.log('CAN START : ' + this.canStart);
		if(this.canStart)
		{
			if(this.snapshotsToShow > this.config['imagesToShow'])
			{
				this.snapshotsToShow = this.config['imagesToShow'];
			}
			else
			{
				var params = event.split('-');
				// console.log(event);
				this.snapshotsToShow = params[1];
			}
			
			//Si la position de l'utilisateur a changé.
			if(event != this.lastIncomingMessage)
			{
				console.log(this.snapshotsToShow);
				this.lastIncomingMessage = event;
				this.showNImages(this.snapshotsToShow);
			}
		}
		
		clearTimeout(this.nouserTimeout);
		this.nouserTimeout = setTimeout(function()
		{
			/*_this.showNImages(0);
			_this.init();
			_this.canStart = false;
			_this.currentMode = "NO-USER";*/
			
				window.location.reload();
			// mos = new mosaic('./config.json', default_parameters);
			
			console.log('NOUSER');
		}, this.config['timeoutNouser']);
	}
	else if((event == "NO-USER" || event == "INCOMING-0" || event == "INCOMING-1") && this.prephaseEnabled)
	{
		/*this.showNImages(0);
		this.init();
		this.canStart = false;
		this.currentMode = "NO-USER";*/
		
			window.location.reload();
		// mos = new mosaic('./config.json', default_parameters);
		
		console.log('NOUSER');
		
		/*this.currentMode = "NO-USER";
		this.showNImages(0);
		this.canStart = false;
		this.init();*/
	}
	// /!\/!\ //
	else if(event.indexOf("SWIPE") != -1)
	{
		if(this.player && this.player.widgets && this.playerIsReady && !this.isSwipe)
		{
			this.isSwipe = true;
			
			if(this.currentMode == 'SEARCH' && this.isMosaicFiltered && !this.player.widgets[0].isAMarkerAhead(this.currentSearchGesture[this.centerId]))
			{
				this.playNextVideo();
			}
			
			//L'utilisateur a fait un swipe left.
			if(event.indexOf("LEFT") != -1)
			{
				this.player.widgets[0].switchToMarker(true, this.currentSearchGesture[this.centerId]);
				if(this.currentMode == 'VIDEO')
				{
					this.removeNotifications();
					this.videoSwipe('left');
				}
				else if(this.currentMode == 'SEARCH' && !this.currentSearchGesture[this.centerId])
				{
					this.removeNotifications();
					this.searchSearchAndSwipe('left');
				}
				else if(this.currentMode == 'SEARCH' && this.currentSearchGesture[this.centerId])
				{
					this.removeNotifications();
					this.searchGestureAndSwipe(this.currentSearchGesture[this.centerId], 'valid', 'left');
				}
			}
			//L'utilisateur a fait un swipe right.
			else if(event.indexOf("RIGHT") != -1)
			{
				this.player.widgets[0].switchToMarker(false, this.currentSearchGesture[this.centerId]);
				if(this.currentMode == 'VIDEO')
				{
					this.removeNotifications();
					this.videoSwipe('right');
				}
				else if(this.currentMode == 'SEARCH' && !this.currentSearchGesture[this.centerId])
				{
					this.removeNotifications();
					this.searchSearchAndSwipe('right');
				}
				else if(this.currentMode == 'SEARCH' && this.currentSearchGesture[this.centerId])
				{
					this.removeNotifications();
					this.searchGestureAndSwipe(this.currentSearchGesture[this.centerId], 'valid', 'right');
				}
			}
			
			//On le fait disparaitre au bout d'un certain temps.
			this.notifySwipeTimeout = setTimeout(function()
			{
				_this.isSwipe = false;
				
				// /!\ //
				_this.removeNotifications();
				
				if(_this.currentMode == 'SEARCH' && !_this.currentSearchGesture[_this.centerId])
				{
					_this.searchSearch();
				}
				else if(_this.currentMode == 'SEARCH' && _this.currentSearchGesture[_this.centerId])
				{
					_this.searchGesture(_this.currentSearchGesture[_this.centerId], 'valid');
				}
				
			}, this.config['timeoutNotifySwipe']);
		}
	}
	else if(event.indexOf("BEND") != -1 || event.indexOf('KNEE-UP') != -1 || event.indexOf('FALL') != -1 || event.indexOf('JUMP') != -1)
	{
		gestureReceived = event.toLowerCase();
		gestureReceived = gestureReceived.replace('wave', 'hello');
		this.currentSearchGesture[centerId] = gestureReceived;
	}
	else if(event.indexOf("HELLO") != -1 && this.canNotifyHelp && !this.areBothPointersHere)
	{
		if(this.currentMode == 'SEARCH')
		{
			this.notifyHelp(false);
		}
		else if(this.currentMode == 'FILTER')
		{
			this.notifyHelp(true);
		}
	}
	
	if(gestureReceived != '')
	{
		if(this.currentMode == "SEARCH" && this.playerIsReady)
		{
			this.player.widgets[0].searchByGesture(gestureReceived);
			this.isCurrentlyInASearchByGesture = this.player.widgets[0].isCurrentlyInASearchByGesture;
			
			this.removeNotifications();
			this.searchGesture(gestureReceived, 'valid');
		}
		else if(this.currentMode == "FILTER")
		{
			if(this.isMosaicFiltered)
			{
				// console.log('FILTER !!!');
				// this.notifySearch1Gesture(gestureReceived, 'valid');
				this.removeNotifications();
				this.filterGesture(gestureReceived, 'valid');
				this.searchFilter(gestureReceived);
			}
		}
		
		if(this.helpDisplayed)
		{
			this.removeHelp();
		}
	}
	// /!\/!\ //
}

/*
 * Chargement du player basé sur le metadataplayer.
*/
mosaic.prototype.loadPlayer = function(newZoomTop, newZoomLeft, newSnWidth, newSnHeight, zoomTop, zoomLeft, timeToGo)
{
	var _this = this;
	
	//On configure les options de lancement.
	IriSP.libFiles.defaultDir = "../lib/";
	IriSP.widgetsDir = "./player/metadataplayer/"
	
	var videoToPlay = this.videos[this.centerId];
	var currentMetadata = this.urls[this.centerId];
	
	var _metadata = {
		url: currentMetadata,
		format: 'ldt'
	};
	
	var _config = {
		gui: {
			zoomTop: zoomTop - this.marginWidth*2,
			zoomLeft: zoomLeft,
			width: newSnWidth,
			height: newSnHeight,
			container: 'LdtPlayer',
			default_options: {
				metadata: _metadata
			},
			css:'./player/metadataplayer/LdtPlayer-core.css',
			widgets: [
				{
					type: "Timeline"
				}
			]
		},
		player:{
			type: 'html5', // player type
			video: videoToPlay,
			live: true,
			height: newSnHeight,
			width: newSnWidth,
			autostart: true
		}
	};
	
	//On positionne le player.
	$('.LdtPlayer').css(
	{
		//display: 'none',
		position: 'absolute',
		'background-color': '#000000',
		top: newZoomTop,
		left: newZoomLeft
	});
	
	//On démarre le player.
	this.player = null;
	
	this.player = new IriSP.Metadataplayer(_config, _metadata);
	
	this.player.onLoad(function()
	{
		if(_this.currentMode == 'NO-USER')
		{
			//On peut s'approcher de la kinect.
			_this.canStart = true;
			console.log('CAN START !');
			// console.log(_this.player);
			// console.log(_this.player.popcorn);
		}
		
		//Lorsque le player est en pause (par exemple lorsque le curseur arrive à la fin de la timeline).
		if(_this.player.popcorn)
		{
			_this.player.popcorn.listen('pause', function()
			{
				//Si l'utilisateur a mis en pause.
				if(_this.userPaused)
				{
				
				}
				//Si la pause est naturelle (fin de la timeline, dézoom, déplacement vers un voisin).
				else
				{
					//Si c'est en mode sans utilisateur.
					if(_this.currentMode == 'NO-USER')
					{
						//On dézoome.
						_this.unzoom();
					}
					//Sinon.
					else
					{
						//Si ce n'est pas causé par un déplacement ou un dézoom.
						if(!_this.currentlyMoving && !_this.currentlyUnzooming)
						{
							//On réinitialise la position du curseur à la prochaine lecture de la vidéo.
							console.log('REINIT');
							//On passe a la video suivante.
							console.log('AUTOMOVE');
							
							//Si on est en mode timeline et qu'on est en pause, c'est probablement que l'user a placé le curseur à la fin.
							if(_this.currentMode != 'TIMELINE')
							{
								_this.playNextVideo();
							}
							//_this.timeToGoAt[parseInt(_this.centerId)] = 0;
							console.log('time to go at to 0');
							// return;
						}
					}
				}
			});
			// console.log('mosaic filtered : ' + _this.isMosaicFiltered);
			
			_this.player.popcorn.on("markersready", function()
			{
				_this.playerIsReady = true;
				
				if(_this.player.widgets[0])
				{
					_this.player.widgets[0].setMouseInteractions(_this.mouseInteractions);
					if(_this.gesturesText.length > 0)
					{
						_this.player.widgets[0].setLang(_this.gesturesText);
					}
				}
				
				if(_this.currentMode == 'VIDEO' || _this.currentMode == 'SEARCH' || _this.currentMode == 'TIMELINE')
				{
					_this.canSwipe = true;
				}
				
				console.log('TIME TO GO AT : ' + _this.timeToGoAt[_this.centerId], _this.centerId, _this.imgs[_this.centerId]);
				
				// if(_this.isMosaicFiltered)
				// {
					if(_this.currentSearchGesture[_this.centerId] == '')
					{
						_this.removeFilter();
					}
					else
					{
						_this.currentMode = 'SEARCH';
						// console.log(_this.currentSearchGesture);
						_this.player.widgets[0].searchByGesture(_this.currentSearchGesture[_this.centerId]);
						_this.isCurrentlyInASearchByGesture = _this.player.widgets[0].isCurrentlyInASearchByGesture;
						
						if(_this.timeToGoAt[_this.centerId] === 0 && _this.player.widgets[0].atLeastOneSearchMarker(_this.currentSearchGesture[_this.centerId]))
						{
							_this.player.widgets[0].goToFirstSearchedMarker(_this.currentSearchGesture[_this.centerId]);
						}
						else
						{
							_this.player.popcorn.currentTime(_this.timeToGoAt[_this.centerId]);
						}
					}
				// }
				// /!\ //
				/*else
				{
					if(_this.player.popcorn)
					{
						_this.player.popcorn.currentTime(_this.timeToGoAt[_this.centerId]);
					}
				}*/
			});
		}
	});
}

/*
 * Permet de tester l'égalité des éléments de deux objets.
 * Pour ce faire on compare les éléments définissant ces objets.
 */
$.fn.equals = function(compareTo)
{
    if (!compareTo || !compareTo.length || this.length!=compareTo.length)
    {
        return false;
    }
    for (var i=0; i<this .length; i++)
    {
        if (this[i]!==compareTo[i])
        {
            return false;
        }
    }
    return true;
}

/*
 * Charge les vidéos, les snapshots et les annotations depuis un fichier json.
*/
mosaic.prototype.loadFromJson = function(path)
{
	var _this = this;
	var i = 0;

	$.getJSON(path, function(data)
	{
		$.each(data, function(key, val)
		{
			$.each(val, function(key_video, val_video)
			{
				$.getJSON(val_video.metadata, function(meta)
				{
					if(_this.config['local'] == 'true')
					{
						_this.affectVideoById(val_video.metadata, meta.medias[0].url.replace('rtmp://media.iri.centrepompidou.fr/ddc_player/', './player/videos/').replace('mp4:', '').replace('video/', '').replace('ldtplatform/', '').replace('.m4v', '.mp4'));
						// console.log(meta.medias[0].url.replace('rtmp://media.iri.centrepompidou.fr/ddc_player/', './player/videos/').replace('mp4:', '').replace('video/', '').replace('ldtplatform/', '').replace('.m4v', '.mp4'));
					}
					else
					{
						_this.affectVideoById(val_video.metadata, meta.medias[0].url.replace('rtmp://', 'http://').replace('/ddc_player/', '/').replace('mp4:', '').replace('.m4v', '.mp4'));
						// console.log(meta.medias[0].url.replace('rtmp://', 'http://').replace('/ddc_player/', '/').replace('mp4:', '').replace('.m4v', '.mp4'));
					}
				});
				_this.imgs[_this.ids[i]] = val_video.snapshot;
				_this.urls[_this.ids[i]] = val_video.metadata;
				//Au départ, on commence à 0 ms dans les vidéos.
				_this.timeToGoAt[_this.ids[i]] = 0;
				// console.log('ids : ' + _this.ids[i]);
				i++;
			});
		});
		// console.log('rdy');
		_this.loadMosaic();
	});
}

/*
 * Affecte une vidéo au tableau des vidéos selon son id
*/
mosaic.prototype.affectVideoById = function(metadata_id, video)
{
	for (i = 0 ; i < this.urls.length ; i++)
	{
		if(this.urls[i] == metadata_id)
		{
			this.videos[i] = video;
			break;
		}
	}
}

/*
 * Rebind keypress pour body.
*/
mosaic.prototype.reaffectKeyPress = function()
{
	var _this = this;
	
	$('body').keypress(function (event)
	{
		_this.manageControlEvents(event);
	});
}

mosaic.prototype.date = function()
{
   var date, h, min, s;
   date = new Date();
   h = date.getHours();
   min = date.getMinutes();
   s = date.getSeconds();
   if (h < 10)
      h = "0" + h;
   if (min < 10)
      min = "0" + min;
   if (s < 10)
      s = "0" + s;
   return (h + ":" + min + ":" + s);
};