1 var osmplayer = osmplayer || {};
  2 (function(exports) {
  3 /*!
  4  * iScroll Lite base on iScroll v4.1.6 ~ Copyright (c) 2011 Matteo Spinelli, http://cubiq.org
  5  * Released under MIT license, http://cubiq.org/license
  6  */
  7 
  8 (function(){
  9 var m = Math,
 10 	mround = function (r) { return r >> 0; },
 11 	vendor = (/webkit/i).test(navigator.appVersion) ? 'webkit' :
 12 		(/firefox/i).test(navigator.userAgent) ? 'Moz' :
 13 		'opera' in window ? 'O' : '',
 14 
 15     // Browser capabilities
 16     isAndroid = (/android/gi).test(navigator.appVersion),
 17     isIDevice = (/iphone|ipad/gi).test(navigator.appVersion),
 18     isPlaybook = (/playbook/gi).test(navigator.appVersion),
 19     isTouchPad = (/hp-tablet/gi).test(navigator.appVersion),
 20 
 21     has3d = 'WebKitCSSMatrix' in window && 'm11' in new WebKitCSSMatrix(),
 22     hasTouch = 'ontouchstart' in window && !isTouchPad,
 23     hasTransform = vendor + 'Transform' in document.documentElement.style,
 24     hasTransitionEnd = isIDevice || isPlaybook,
 25 
 26 	nextFrame = (function() {
 27 	    return window.requestAnimationFrame
 28 			|| window.webkitRequestAnimationFrame
 29 			|| window.mozRequestAnimationFrame
 30 			|| window.oRequestAnimationFrame
 31 			|| window.msRequestAnimationFrame
 32 			|| function(callback) { return setTimeout(callback, 17); }
 33 	})(),
 34 	cancelFrame = (function () {
 35 	    return window.cancelRequestAnimationFrame
 36 			|| window.webkitCancelAnimationFrame
 37 			|| window.webkitCancelRequestAnimationFrame
 38 			|| window.mozCancelRequestAnimationFrame
 39 			|| window.oCancelRequestAnimationFrame
 40 			|| window.msCancelRequestAnimationFrame
 41 			|| clearTimeout
 42 	})(),
 43 
 44 	// Events
 45 	RESIZE_EV = 'onorientationchange' in window ? 'orientationchange' : 'resize',
 46 	START_EV = hasTouch ? 'touchstart' : 'mousedown',
 47 	MOVE_EV = hasTouch ? 'touchmove' : 'mousemove',
 48 	END_EV = hasTouch ? 'touchend' : 'mouseup',
 49 	CANCEL_EV = hasTouch ? 'touchcancel' : 'mouseup',
 50 
 51 	// Helpers
 52 	trnOpen = 'translate' + (has3d ? '3d(' : '('),
 53 	trnClose = has3d ? ',0)' : ')',
 54 
 55 	// Constructor
 56 	iScroll = function (el, options) {
 57 		var that = this,
 58 			doc = document,
 59 			i;
 60 
 61 		that.wrapper = typeof el == 'object' ? el : doc.getElementById(el);
 62 		that.wrapper.style.overflow = 'hidden';
 63 		that.scroller = that.wrapper.children[0];
 64 
 65 		// Default options
 66 		that.options = {
 67 			hScroll: true,
 68 			vScroll: true,
 69 			x: 0,
 70 			y: 0,
 71 			bounce: true,
 72 			bounceLock: false,
 73 			momentum: true,
 74 			lockDirection: true,
 75 			useTransform: true,
 76 			useTransition: false,
 77 
 78 			// Events
 79 			onRefresh: null,
 80 			onBeforeScrollStart: function (e) { e.preventDefault(); },
 81 			onScrollStart: null,
 82 			onBeforeScrollMove: null,
 83 			onScrollMove: null,
 84 			onBeforeScrollEnd: null,
 85 			onScrollEnd: null,
 86 			onTouchEnd: null,
 87 			onDestroy: null
 88 		};
 89 
 90 		// User defined options
 91 		for (i in options) that.options[i] = options[i];
 92 
 93 		// Set starting position
 94 		that.x = that.options.x;
 95 		that.y = that.options.y;
 96 
 97 		// Normalize options
 98 		that.options.useTransform = hasTransform ? that.options.useTransform : false;
 99 		that.options.hScrollbar = that.options.hScroll && that.options.hScrollbar;
100 		that.options.vScrollbar = that.options.vScroll && that.options.vScrollbar;
101 		that.options.useTransition = hasTransitionEnd && that.options.useTransition;
102 
103 		// Set some default styles
104 		that.scroller.style[vendor + 'TransitionProperty'] = that.options.useTransform ? '-' + vendor.toLowerCase() + '-transform' : 'top left';
105 		that.scroller.style[vendor + 'TransitionDuration'] = '0';
106 		that.scroller.style[vendor + 'TransformOrigin'] = '0 0';
107 		if (that.options.useTransition) that.scroller.style[vendor + 'TransitionTimingFunction'] = 'cubic-bezier(0.33,0.66,0.66,1)';
108 		
109 		if (that.options.useTransform) that.scroller.style[vendor + 'Transform'] = trnOpen + that.x + 'px,' + that.y + 'px' + trnClose;
110 		else that.scroller.style.cssText += ';position:absolute;top:' + that.y + 'px;left:' + that.x + 'px';
111 
112 		that.refresh();
113 
114 		that._bind(RESIZE_EV, window);
115 		that._bind(START_EV);
116 		if (!hasTouch) that._bind('mouseout', that.wrapper);
117 	};
118 
119 // Prototype
120 iScroll.prototype = {
121 	enabled: true,
122 	x: 0,
123 	y: 0,
124 	steps: [],
125 	scale: 1,
126 	
127 	handleEvent: function (e) {
128 		var that = this;
129 		switch(e.type) {
130 			case START_EV:
131 				if (!hasTouch && e.button !== 0) return;
132 				that._start(e);
133 				break;
134 			case MOVE_EV: that._move(e); break;
135 			case END_EV:
136 			case CANCEL_EV: that._end(e); break;
137 			case RESIZE_EV: that._resize(); break;
138 			case 'mouseout': that._mouseout(e); break;
139 			case 'webkitTransitionEnd': that._transitionEnd(e); break;
140 		}
141 	},
142 
143 	_resize: function () {
144 		this.refresh();
145 	},
146 	
147 	_pos: function (x, y) {
148 		x = this.hScroll ? x : 0;
149 		y = this.vScroll ? y : 0;
150 
151 		if (this.options.useTransform) {
152 			this.scroller.style[vendor + 'Transform'] = trnOpen + x + 'px,' + y + 'px' + trnClose + ' scale(' + this.scale + ')';
153 		} else {
154 			x = mround(x);
155 			y = mround(y);
156 			this.scroller.style.left = x + 'px';
157 			this.scroller.style.top = y + 'px';
158 		}
159 
160 		this.x = x;
161 		this.y = y;
162 	},
163 
164 	_start: function (e) {
165 		var that = this,
166 			point = hasTouch ? e.touches[0] : e,
167 			matrix, x, y;
168 
169 		if (!that.enabled) return;
170 
171 		if (that.options.onBeforeScrollStart) that.options.onBeforeScrollStart.call(that, e);
172 		
173 		if (that.options.useTransition) that._transitionTime(0);
174 
175 		that.moved = false;
176 		that.animating = false;
177 		that.zoomed = false;
178 		that.distX = 0;
179 		that.distY = 0;
180 		that.absDistX = 0;
181 		that.absDistY = 0;
182 		that.dirX = 0;
183 		that.dirY = 0;
184 
185 		if (that.options.momentum) {
186 			if (that.options.useTransform) {
187 				// Very lame general purpose alternative to CSSMatrix
188 				matrix = getComputedStyle(that.scroller, null)[vendor + 'Transform'].replace(/[^0-9-.,]/g, '').split(',');
189 				x = matrix[4] * 1;
190 				y = matrix[5] * 1;
191 			} else {
192 				x = getComputedStyle(that.scroller, null).left.replace(/[^0-9-]/g, '') * 1;
193 				y = getComputedStyle(that.scroller, null).top.replace(/[^0-9-]/g, '') * 1;
194 			}
195 			
196 			if (x != that.x || y != that.y) {
197 				if (that.options.useTransition) that._unbind('webkitTransitionEnd');
198 				else cancelFrame(that.aniTime);
199 				that.steps = [];
200 				that._pos(x, y);
201 			}
202 		}
203 
204 		that.startX = that.x;
205 		that.startY = that.y;
206 		that.pointX = point.pageX;
207 		that.pointY = point.pageY;
208 
209 		that.startTime = e.timeStamp || Date.now();
210 
211 		if (that.options.onScrollStart) that.options.onScrollStart.call(that, e);
212 
213 		that._bind(MOVE_EV);
214 		that._bind(END_EV);
215 		that._bind(CANCEL_EV);
216 	},
217 	
218 	_move: function (e) {
219 		var that = this,
220 			point = hasTouch ? e.touches[0] : e,
221 			deltaX = point.pageX - that.pointX,
222 			deltaY = point.pageY - that.pointY,
223 			newX = that.x + deltaX,
224 			newY = that.y + deltaY,
225 			timestamp = e.timeStamp || Date.now();
226 
227 		if (that.options.onBeforeScrollMove) that.options.onBeforeScrollMove.call(that, e);
228 
229 		that.pointX = point.pageX;
230 		that.pointY = point.pageY;
231 
232 		// Slow down if outside of the boundaries
233 		if (newX > 0 || newX < that.maxScrollX) {
234 			newX = that.options.bounce ? that.x + (deltaX / 2) : newX >= 0 || that.maxScrollX >= 0 ? 0 : that.maxScrollX;
235 		}
236 		if (newY > 0 || newY < that.maxScrollY) { 
237 			newY = that.options.bounce ? that.y + (deltaY / 2) : newY >= 0 || that.maxScrollY >= 0 ? 0 : that.maxScrollY;
238 		}
239 
240 		that.distX += deltaX;
241 		that.distY += deltaY;
242 		that.absDistX = m.abs(that.distX);
243 		that.absDistY = m.abs(that.distY);
244 
245 		if (that.absDistX < 6 && that.absDistY < 6) {
246 			return;
247 		}
248 
249 		// Lock direction
250 		if (that.options.lockDirection) {
251 			if (that.absDistX > that.absDistY + 5) {
252 				newY = that.y;
253 				deltaY = 0;
254 			} else if (that.absDistY > that.absDistX + 5) {
255 				newX = that.x;
256 				deltaX = 0;
257 			}
258 		}
259 
260 		that.moved = true;
261 		that._pos(newX, newY);
262 		that.dirX = deltaX > 0 ? -1 : deltaX < 0 ? 1 : 0;
263 		that.dirY = deltaY > 0 ? -1 : deltaY < 0 ? 1 : 0;
264 
265 		if (timestamp - that.startTime > 300) {
266 			that.startTime = timestamp;
267 			that.startX = that.x;
268 			that.startY = that.y;
269 		}
270 		
271 		if (that.options.onScrollMove) that.options.onScrollMove.call(that, e);
272 	},
273 	
274 	_end: function (e) {
275 		if (hasTouch && e.touches.length != 0) return;
276 
277 		var that = this,
278 			point = hasTouch ? e.changedTouches[0] : e,
279 			target, ev,
280 			momentumX = { dist:0, time:0 },
281 			momentumY = { dist:0, time:0 },
282 			duration = (e.timeStamp || Date.now()) - that.startTime,
283 			newPosX = that.x,
284 			newPosY = that.y,
285 			newDuration;
286 
287 		that._unbind(MOVE_EV);
288 		that._unbind(END_EV);
289 		that._unbind(CANCEL_EV);
290 
291 		if (that.options.onBeforeScrollEnd) that.options.onBeforeScrollEnd.call(that, e);
292 
293 		if (!that.moved) {
294 			if (hasTouch) {
295 				// Find the last touched element
296 				target = point.target;
297 				while (target.nodeType != 1) target = target.parentNode;
298 
299 				if (target.tagName != 'SELECT' && target.tagName != 'INPUT' && target.tagName != 'TEXTAREA') {
300 					ev = document.createEvent('MouseEvents');
301 					ev.initMouseEvent('click', true, true, e.view, 1,
302 						point.screenX, point.screenY, point.clientX, point.clientY,
303 						e.ctrlKey, e.altKey, e.shiftKey, e.metaKey,
304 						0, null);
305 					ev._fake = true;
306 					target.dispatchEvent(ev);
307 				}
308 			}
309 
310 			that._resetPos(200);
311 
312 			if (that.options.onTouchEnd) that.options.onTouchEnd.call(that, e);
313 			return;
314 		}
315 
316 		if (duration < 300 && that.options.momentum) {
317 			momentumX = newPosX ? that._momentum(newPosX - that.startX, duration, -that.x, that.scrollerW - that.wrapperW + that.x, that.options.bounce ? that.wrapperW : 0) : momentumX;
318 			momentumY = newPosY ? that._momentum(newPosY - that.startY, duration, -that.y, (that.maxScrollY < 0 ? that.scrollerH - that.wrapperH + that.y : 0), that.options.bounce ? that.wrapperH : 0) : momentumY;
319 
320 			newPosX = that.x + momentumX.dist;
321 			newPosY = that.y + momentumY.dist;
322 
323  			if ((that.x > 0 && newPosX > 0) || (that.x < that.maxScrollX && newPosX < that.maxScrollX)) momentumX = { dist:0, time:0 };
324  			if ((that.y > 0 && newPosY > 0) || (that.y < that.maxScrollY && newPosY < that.maxScrollY)) momentumY = { dist:0, time:0 };
325 		}
326 
327 		if (momentumX.dist || momentumY.dist) {
328 			newDuration = m.max(m.max(momentumX.time, momentumY.time), 10);
329 
330 			that.scrollTo(mround(newPosX), mround(newPosY), newDuration);
331 
332 			if (that.options.onTouchEnd) that.options.onTouchEnd.call(that, e);
333 			return;
334 		}
335 
336 		that._resetPos(200);
337 		if (that.options.onTouchEnd) that.options.onTouchEnd.call(that, e);
338 	},
339 	
340 	_resetPos: function (time) {
341 		var that = this,
342 			resetX = that.x >= 0 ? 0 : that.x < that.maxScrollX ? that.maxScrollX : that.x,
343 			resetY = that.y >= 0 || that.maxScrollY > 0 ? 0 : that.y < that.maxScrollY ? that.maxScrollY : that.y;
344 
345 		if (resetX == that.x && resetY == that.y) {
346 			if (that.moved) {
347 				if (that.options.onScrollEnd) that.options.onScrollEnd.call(that);		// Execute custom code on scroll end
348 				that.moved = false;
349 			}
350 
351 			return;
352 		}
353 
354 		that.scrollTo(resetX, resetY, time || 0);
355 	},
356 	
357 	_mouseout: function (e) {
358 		var t = e.relatedTarget;
359 
360 		if (!t) {
361 			this._end(e);
362 			return;
363 		}
364 
365 		while (t = t.parentNode) if (t == this.wrapper) return;
366 		
367 		this._end(e);
368 	},
369 
370 	_transitionEnd: function (e) {
371 		var that = this;
372 
373 		if (e.target != that.scroller) return;
374 
375 		that._unbind('webkitTransitionEnd');
376 		
377 		that._startAni();
378 	},
379 
380 	/**
381 	 *
382 	 * Utilities
383 	 *
384 	 */
385 	_startAni: function () {
386 		var that = this,
387 			startX = that.x, startY = that.y,
388 			startTime = Date.now(),
389 			step, easeOut,
390 			animate;
391 
392 		if (that.animating) return;
393 
394 		if (!that.steps.length) {
395 			that._resetPos(400);
396 			return;
397 		}
398 
399 		step = that.steps.shift();
400 
401 		if (step.x == startX && step.y == startY) step.time = 0;
402 
403 		that.animating = true;
404 		that.moved = true;
405 
406 		if (that.options.useTransition) {
407 			that._transitionTime(step.time);
408 			that._pos(step.x, step.y);
409 			that.animating = false;
410 			if (step.time) that._bind('webkitTransitionEnd');
411 			else that._resetPos(0);
412 			return;
413 		}
414 		
415 		animate = function () {
416 			var now = Date.now(),
417 				newX, newY;
418 
419 			if (now >= startTime + step.time) {
420 				that._pos(step.x, step.y);
421 				that.animating = false;
422 				if (that.options.onAnimationEnd) that.options.onAnimationEnd.call(that);			// Execute custom code on animation end
423 				that._startAni();
424 				return;
425 			}
426 
427 			now = (now - startTime) / step.time - 1;
428 			easeOut = m.sqrt(1 - now * now);
429 			newX = (step.x - startX) * easeOut + startX;
430 			newY = (step.y - startY) * easeOut + startY;
431 			that._pos(newX, newY);
432 			if (that.animating) that.aniTime = nextFrame(animate);
433 		};
434 		
435 		animate();
436 	},
437 
438 	_transitionTime: function (time) {
439 		this.scroller.style[vendor + 'TransitionDuration'] = time + 'ms';
440 	},
441 	
442 	_momentum: function (dist, time, maxDistUpper, maxDistLower, size) {
443 		var deceleration = 0.0006,
444 			speed = m.abs(dist) / time,
445 			newDist = (speed * speed) / (2 * deceleration),
446 			newTime = 0, outsideDist = 0;
447 
448 		// Proportinally reduce speed if we are outside of the boundaries 
449 		if (dist > 0 && newDist > maxDistUpper) {
450 			outsideDist = size / (6 / (newDist / speed * deceleration));
451 			maxDistUpper = maxDistUpper + outsideDist;
452 			speed = speed * maxDistUpper / newDist;
453 			newDist = maxDistUpper;
454 		} else if (dist < 0 && newDist > maxDistLower) {
455 			outsideDist = size / (6 / (newDist / speed * deceleration));
456 			maxDistLower = maxDistLower + outsideDist;
457 			speed = speed * maxDistLower / newDist;
458 			newDist = maxDistLower;
459 		}
460 
461 		newDist = newDist * (dist < 0 ? -1 : 1);
462 		newTime = speed / deceleration;
463 
464 		return { dist: newDist, time: mround(newTime) };
465 	},
466 
467 	_offset: function (el) {
468 		var left = -el.offsetLeft,
469 			top = -el.offsetTop;
470 			
471 		while (el = el.offsetParent) {
472 			left -= el.offsetLeft;
473 			top -= el.offsetTop;
474 		} 
475 
476 		return { left: left, top: top };
477 	},
478 
479 	_bind: function (type, el, bubble) {
480 		(el || this.scroller).addEventListener(type, this, !!bubble);
481 	},
482 
483 	_unbind: function (type, el, bubble) {
484 		(el || this.scroller).removeEventListener(type, this, !!bubble);
485 	},
486 
487 
488 	/**
489 	 *
490 	 * Public methods
491 	 *
492 	 */
493 	destroy: function () {
494 		var that = this;
495 
496 		that.scroller.style[vendor + 'Transform'] = '';
497 
498 		// Remove the event listeners
499 		that._unbind(RESIZE_EV, window);
500 		that._unbind(START_EV);
501 		that._unbind(MOVE_EV);
502 		that._unbind(END_EV);
503 		that._unbind(CANCEL_EV);
504 		that._unbind('mouseout', that.wrapper);
505 		if (that.options.useTransition) that._unbind('webkitTransitionEnd');
506 		
507 		if (that.options.onDestroy) that.options.onDestroy.call(that);
508 	},
509 
510 	refresh: function () {
511 		var that = this,
512 			offset;
513 
514 		that.wrapperW = that.wrapper.clientWidth;
515 		that.wrapperH = that.wrapper.clientHeight;
516 
517 		that.scrollerW = that.scroller.offsetWidth;
518 		that.scrollerH = that.scroller.offsetHeight;
519 		that.maxScrollX = that.wrapperW - that.scrollerW;
520 		that.maxScrollY = that.wrapperH - that.scrollerH;
521 		that.dirX = 0;
522 		that.dirY = 0;
523 
524 		that.hScroll = that.options.hScroll && that.maxScrollX < 0;
525 		that.vScroll = that.options.vScroll && (!that.options.bounceLock && !that.hScroll || that.scrollerH > that.wrapperH);
526 
527 		offset = that._offset(that.wrapper);
528 		that.wrapperOffsetLeft = -offset.left;
529 		that.wrapperOffsetTop = -offset.top;
530 
531 
532 		that.scroller.style[vendor + 'TransitionDuration'] = '0';
533 
534 		that._resetPos(200);
535 	},
536 
537 	scrollTo: function (x, y, time, relative) {
538 		var that = this,
539 			step = x,
540 			i, l;
541 
542 		that.stop();
543 
544 		if (!step.length) step = [{ x: x, y: y, time: time, relative: relative }];
545 		
546 		for (i=0, l=step.length; i<l; i++) {
547 			if (step[i].relative) { step[i].x = that.x - step[i].x; step[i].y = that.y - step[i].y; }
548 			that.steps.push({ x: step[i].x, y: step[i].y, time: step[i].time || 0 });
549 		}
550 
551 		that._startAni();
552 	},
553 
554 	scrollToElement: function (el, time) {
555 		var that = this, pos;
556 		el = el.nodeType ? el : that.scroller.querySelector(el);
557 		if (!el) return;
558 
559 		pos = that._offset(el);
560 		pos.left += that.wrapperOffsetLeft;
561 		pos.top += that.wrapperOffsetTop;
562 
563 		pos.left = pos.left > 0 ? 0 : pos.left < that.maxScrollX ? that.maxScrollX : pos.left;
564 		pos.top = pos.top > 0 ? 0 : pos.top < that.maxScrollY ? that.maxScrollY : pos.top;
565 		time = time === undefined ? m.max(m.abs(pos.left)*2, m.abs(pos.top)*2) : time;
566 
567 		that.scrollTo(pos.left, pos.top, time);
568 	},
569 
570 	disable: function () {
571 		this.stop();
572 		this._resetPos(0);
573 		this.enabled = false;
574 
575 		// If disabled after touchstart we make sure that there are no left over events
576 		this._unbind(MOVE_EV);
577 		this._unbind(END_EV);
578 		this._unbind(CANCEL_EV);
579 	},
580 	
581 	enable: function () {
582 		this.enabled = true;
583 	},
584 	
585 	stop: function () {
586 		cancelFrame(this.aniTime);
587 		this.steps = [];
588 		this.moved = false;
589 		this.animating = false;
590 	}
591 };
592 
593 if (typeof exports !== 'undefined') exports.iScroll = iScroll;
594 else window.iScroll = iScroll;
595 
596 })();
597 })(osmplayer);
598 /**
599  * @constructor
600  * @extends minplayer.display
601  * @class This class creates the playlist functionality for the minplayer.
602  *
603  * @param {object} context The jQuery context.
604  * @param {object} options This components options.
605  */
606 osmplayer.playlist = function(context, options) {
607 
608   // Derive from display
609   minplayer.display.call(this, 'playlist', context, options);
610 };
611 
612 /** Derive from minplayer.display. */
613 osmplayer.playlist.prototype = new minplayer.display();
614 
615 /** Reset the constructor. */
616 osmplayer.playlist.prototype.constructor = osmplayer.playlist;
617 
618 /**
619  * Returns the default options for this plugin.
620  *
621  * @param {object} options The default options for this plugin.
622  */
623 osmplayer.playlist.prototype.defaultOptions = function(options) {
624   options.vertical = true;
625   options.playlist = '';
626   options.pageLimit = 10;
627   options.autoNext = true;
628   options.shuffle = false;
629   options.loop = false;
630   options.hysteresis = 40;
631   options.scrollSpeed = 20;
632   options.scrollMode = 'auto';
633   minplayer.display.prototype.defaultOptions.call(this, options);
634 };
635 
636 /**
637  * @see minplayer.plugin#construct
638  */
639 osmplayer.playlist.prototype.construct = function() {
640 
641   /** The nodes within this playlist. */
642   this.nodes = [];
643 
644   // Current page.
645   this.page = -1;
646 
647   // The total amount of nodes.
648   this.totalItems = 0;
649 
650   // The current loaded item index.
651   this.currentItem = -1;
652 
653   // The play playqueue.
654   this.playqueue = [];
655 
656   // The playqueue position.
657   this.playqueuepos = 0;
658 
659   // The current playlist.
660   this.playlist = this.options.playlist;
661 
662   // Create the scroll bar.
663   this.scroll = null;
664 
665   // Create our orientation variable.
666   this.orient = {
667     pos: this.options.vertical ? 'y' : 'x',
668     pagePos: this.options.vertical ? 'pageY' : 'pageX',
669     offset: this.options.vertical ? 'top' : 'left',
670     wrapperSize: this.options.vertical ? 'wrapperH' : 'wrapperW',
671     minScroll: this.options.vertical ? 'minScrollY' : 'minScrollX',
672     maxScroll: this.options.vertical ? 'maxScrollY' : 'maxScrollX',
673     size: this.options.vertical ? 'height' : 'width'
674   };
675 
676   // Create the pager.
677   this.pager = this.create('pager', 'osmplayer');
678   this.pager.ubind(this.uuid + ':nextPage', (function(playlist) {
679     return function(event) {
680       playlist.nextPage();
681     };
682   })(this));
683   this.pager.ubind(this.uuid + ':prevPage', (function(playlist) {
684     return function(event) {
685       playlist.prevPage();
686     };
687   })(this));
688 
689   // Call the minplayer plugin constructor.
690   minplayer.display.prototype.construct.call(this);
691 
692   // Load the "next" item.
693   this.hasPlaylist = this.next();
694 
695   // Say that we are ready.
696   this.ready();
697 };
698 
699 /**
700  * @see minplayer.plugin.onAdded
701  */
702 osmplayer.playlist.prototype.onAdded = function(plugin) {
703 
704   // Get the media.
705   if (this.options.autoNext) {
706 
707     // Get the player from this plugin.
708     plugin.get('player', (function(playlist) {
709       return function(player) {
710         player.ubind(playlist.uuid + ':player_ended', function(event) {
711           if (playlist.hasPlaylist) {
712             if (typeof player.options.originalAutoPlay == 'undefined') {
713               player.options.originalAutoPlay = player.options.autoplay;
714             }
715             player.options.autoplay = true;
716             playlist.next();
717           }
718         });
719       };
720     })(this));
721   }
722 };
723 
724 /**
725  * Wrapper around the scroll scrollTo method.
726  *
727  * @param {number} pos The position you would like to set the list.
728  * @param {boolean} relative If this is a relative position change.
729  */
730 osmplayer.playlist.prototype.scrollTo = function(pos, relative) {
731   if (this.scroll) {
732     this.scroll.options.hideScrollbar = false;
733     if (this.options.vertical) {
734       this.scroll.scrollTo(0, pos, 0, relative);
735     }
736     else {
737       this.scroll.scrollTo(pos, 0, 0, relative);
738     }
739     this.scroll.options.hideScrollbar = true;
740   }
741 };
742 
743 /**
744  * Refresh the scrollbar.
745  */
746 osmplayer.playlist.prototype.refreshScroll = function() {
747 
748   // Make sure that our window has the addEventListener to keep IE happy.
749   if (!window.addEventListener) {
750     setTimeout((function(playlist) {
751       return function() {
752         playlist.refreshScroll.call(playlist);
753       };
754     })(this), 200);
755     return;
756   }
757 
758   // Check the size of the playlist.
759   var list = this.elements.list;
760   var scroll = this.elements.scroll;
761 
762   // Destroy the scroll bar first.
763   if (this.scroll) {
764     this.scroll.scrollTo(0, 0);
765     this.scroll.destroy();
766     this.scroll = null;
767     this.elements.list
768         .unbind('mousemove')
769         .unbind('mouseenter')
770         .unbind('mouseleave');
771   }
772 
773   // Need to force the width of the list.
774   if (!this.options.vertical) {
775     var listSize = 0;
776     jQuery.each(this.elements.list.children(), function() {
777       listSize += jQuery(this).outerWidth();
778     });
779     this.elements.list.width(listSize);
780   }
781 
782   // Check to see if we should add a scroll bar functionality.
783   if ((list.length > 0) &&
784       (scroll.length > 0) &&
785       (list[this.orient.size]() > scroll[this.orient.size]())) {
786 
787     // Setup the osmplayer.iScroll component.
788     this.scroll = new osmplayer.iScroll(this.elements.scroll.eq(0)[0], {
789       hScroll: !this.options.vertical,
790       hScrollbar: !this.options.vertical,
791       vScroll: this.options.vertical,
792       vScrollbar: this.options.vertical,
793       hideScrollbar: (this.options.scrollMode !== 'none')
794     });
795 
796     // Use autoScroll for non-touch devices.
797     if ((this.options.scrollMode == 'auto') && !minplayer.hasTouch) {
798 
799       // Bind to the mouse events for autoscrolling.
800       this.elements.list.bind('mousemove', (function(playlist) {
801         return function(event) {
802           event.preventDefault();
803           var offset = playlist.display.offset()[playlist.orient.offset];
804           playlist.mousePos = event[playlist.orient.pagePos];
805           playlist.mousePos -= offset;
806         };
807       })(this)).bind('mouseenter', (function(playlist) {
808         return function(event) {
809           event.preventDefault();
810           playlist.scrolling = true;
811           var setScroll = function() {
812             if (playlist.scrolling) {
813               var scrollSize = playlist.scroll[playlist.orient.wrapperSize];
814               var scrollMid = (scrollSize / 2);
815               var delta = playlist.mousePos - scrollMid;
816               if (Math.abs(delta) > playlist.options.hysteresis) {
817                 var hyst = playlist.options.hysteresis;
818                 hyst *= (delta > 0) ? -1 : 0;
819                 delta = (playlist.options.scrollSpeed * (delta + hyst));
820                 delta /= scrollMid;
821                 var pos = playlist.scroll[playlist.orient.pos] - delta;
822                 var min = playlist.scroll[playlist.orient.minScroll] || 0;
823                 var max = playlist.scroll[playlist.orient.maxScroll];
824                 if (pos >= min) {
825                   playlist.scrollTo(min);
826                 }
827                 else if (pos <= max) {
828                   playlist.scrollTo(max);
829                 }
830                 else {
831                   playlist.scrollTo(delta, true);
832                 }
833               }
834 
835               // Set timeout to try again.
836               setTimeout(setScroll, 30);
837             }
838           };
839           setScroll();
840         };
841       })(this)).bind('mouseleave', (function(playlist) {
842         return function(event) {
843           event.preventDefault();
844           playlist.scrolling = false;
845         };
846       })(this));
847     }
848 
849     this.scroll.refresh();
850     this.scroll.scrollTo(0, 0, 200);
851   }
852 };
853 
854 /**
855  * Adds a new node to the playlist.
856  *
857  * @param {object} node The node that you would like to add to the playlist.
858  */
859 osmplayer.playlist.prototype.addNode = function(node) {
860 
861   // Get the current index for this node.
862   var index = this.nodes.length;
863 
864   // Create the teaser object.
865   var teaser = this.create('teaser', 'osmplayer', this.elements.list);
866 
867   // Set the node for this teaser.
868   teaser.setNode(node);
869 
870   // Bind to when it loads.
871   teaser.ubind(this.uuid + ':nodeLoad', (function(playlist) {
872     return function(event, data) {
873       playlist.loadItem(index, true);
874     };
875   })(this));
876 
877   // Add this to our nodes array.
878   this.nodes.push(teaser);
879 };
880 
881 /**
882  * Sets the playlist.
883  *
884  * @param {object} playlist The playlist object.
885  * @param {integer} loadIndex The index of the item to load.
886  */
887 osmplayer.playlist.prototype.set = function(playlist, loadIndex) {
888 
889   // Check to make sure the playlist is an object.
890   if (typeof playlist !== 'object') {
891     this.trigger('error', 'Playlist must be an object to set');
892     return;
893   }
894 
895   // Check to make sure the playlist has correct format.
896   if (!playlist.hasOwnProperty('total_rows')) {
897     this.trigger('error', 'Unknown playlist format.');
898     return;
899   }
900 
901   // Make sure the playlist has some rows.
902   if (playlist.total_rows && playlist.nodes.length) {
903 
904     // Set the total rows.
905     this.totalItems = playlist.total_rows;
906     this.currentItem = 0;
907 
908     // Show or hide the next page if there is or is not a next page.
909     if ((((this.page + 1) * this.options.pageLimit) >= this.totalItems) ||
910         (this.totalItems == playlist.nodes.length)) {
911       this.pager.nextPage.hide();
912     }
913     else {
914       this.pager.nextPage.show();
915     }
916 
917     var teaser = null;
918     var numNodes = playlist.nodes.length;
919     this.elements.list.empty();
920     this.nodes = [];
921 
922     // Iterate through all the nodes.
923     for (var index = 0; index < numNodes; index++) {
924 
925       // Add this node to the playlist.
926       this.addNode(playlist.nodes[index]);
927 
928       // If the index is equal to the loadIndex.
929       if (loadIndex === index) {
930         this.loadItem(index);
931       }
932     }
933 
934     // Refresh the sizes.
935     this.refreshScroll();
936 
937     // Trigger that the playlist has loaded.
938     this.trigger('playlistLoad', playlist);
939   }
940 
941   // Show that we are no longer busy.
942   if (this.elements.playlist_busy) {
943     this.elements.playlist_busy.hide();
944   }
945 };
946 
947 /**
948  * Stores the current playlist state in the playqueue.
949  */
950 osmplayer.playlist.prototype.setQueue = function() {
951 
952   // Add this item to the playqueue.
953   this.playqueue.push({
954     page: this.page,
955     item: this.currentItem
956   });
957 
958   // Store the current playqueue position.
959   this.playqueuepos = this.playqueue.length;
960 };
961 
962 /**
963  * Loads the next item.
964  *
965  * @return {boolean} TRUE if loaded, FALSE if not.
966  */
967 osmplayer.playlist.prototype.next = function() {
968   var item = 0, page = this.page;
969 
970   // See if we are at the front of the playqueue.
971   if (this.playqueuepos >= this.playqueue.length) {
972 
973     // If this is shuffle, then load a random item.
974     if (this.options.shuffle) {
975       item = Math.floor(Math.random() * this.totalItems);
976       page = Math.floor(item / this.options.pageLimit);
977       item = item % this.options.pageLimit;
978       return this.load(page, item);
979     }
980     else {
981 
982       // Otherwise, increment the current item by one.
983       item = (this.currentItem + 1);
984       if (item >= this.nodes.length) {
985         return this.load(page + 1, 0);
986       }
987       else {
988         return this.loadItem(item);
989       }
990     }
991   }
992   else {
993 
994     // Load the next item in the playqueue.
995     this.playqueuepos = this.playqueuepos + 1;
996     var currentQueue = this.playqueue[this.playqueuepos];
997     return this.load(currentQueue.page, currentQueue.item);
998   }
999 };
1000 
1001 /**
1002  * Loads the previous item.
1003  *
1004  * @return {boolean} TRUE if loaded, FALSE if not.
1005  */
1006 osmplayer.playlist.prototype.prev = function() {
1007 
1008   // Move back into the playqueue.
1009   this.playqueuepos = this.playqueuepos - 1;
1010   this.playqueuepos = (this.playqueuepos < 0) ? 0 : this.playqueuepos;
1011   var currentQueue = this.playqueue[this.playqueuepos];
1012   if (currentQueue) {
1013     return this.load(currentQueue.page, currentQueue.item);
1014   }
1015   return false;
1016 };
1017 
1018 /**
1019  * Loads a playlist node.
1020  *
1021  * @param {number} index The index of the item you would like to load.
1022  * @return {boolean} TRUE if loaded, FALSE if not.
1023  */
1024 osmplayer.playlist.prototype.loadItem = function(index, autoplay) {
1025   if (index < this.nodes.length) {
1026     this.setQueue();
1027 
1028     // Get the teaser at the current index and deselect it.
1029     var teaser = this.nodes[this.currentItem];
1030     teaser.select(false);
1031     this.currentItem = index;
1032 
1033     // Get the new teaser and select it.
1034     teaser = this.nodes[index];
1035     teaser.select(true);
1036     teaser.node.autoplay = !!autoplay;
1037     this.trigger('nodeLoad', teaser.node);
1038     return true;
1039   }
1040 
1041   return false;
1042 };
1043 
1044 /**
1045  * Loads the next page.
1046  *
1047  * @param {integer} loadIndex The index of the item to load.
1048  * @return {boolean} TRUE if loaded, FALSE if not.
1049  */
1050 osmplayer.playlist.prototype.nextPage = function(loadIndex) {
1051   return this.load(this.page + 1, loadIndex);
1052 };
1053 
1054 /**
1055  * Loads the previous page.
1056  *
1057  * @param {integer} loadIndex The index of the item to load.
1058  * @return {boolean} TRUE if loaded, FALSE if not.
1059  */
1060 osmplayer.playlist.prototype.prevPage = function(loadIndex) {
1061   return this.load(this.page - 1, loadIndex);
1062 };
1063 
1064 /**
1065  * Loads a playlist.
1066  *
1067  * @param {integer} page The page to load.
1068  * @param {integer} loadIndex The index of the item to load.
1069  * @return {boolean} TRUE if loaded, FALSE if not.
1070  */
1071 osmplayer.playlist.prototype.load = function(page, loadIndex) {
1072 
1073   // If the playlist and pages are the same, then no need to load.
1074   if ((this.playlist == this.options.playlist) && (page == this.page)) {
1075     return this.loadItem(loadIndex);
1076   }
1077 
1078   // Set the new playlist.
1079   this.playlist = this.options.playlist;
1080 
1081   // Return if there aren't any playlists to play.
1082   if (!this.playlist) {
1083     return false;
1084   }
1085 
1086   // Determine if we need to loop.
1087   var maxPages = Math.floor(this.totalItems / this.options.pageLimit);
1088   if (page > maxPages) {
1089     if (this.options.loop) {
1090       page = 0;
1091       loadIndex = 0;
1092     }
1093     else {
1094       return false;
1095     }
1096   }
1097 
1098   // Say that we are busy.
1099   if (this.elements.playlist_busy) {
1100     this.elements.playlist_busy.show();
1101   }
1102 
1103   // Normalize the page.
1104   page = page || 0;
1105   page = (page < 0) ? 0 : page;
1106 
1107   // Set the queue.
1108   this.setQueue();
1109 
1110   // Set the new page.
1111   this.page = page;
1112 
1113   // Hide or show the page based on if we are on the first page.
1114   if (this.page === 0) {
1115     this.pager.prevPage.hide();
1116   }
1117   else {
1118     this.pager.prevPage.show();
1119   }
1120 
1121   // If the playlist is an object, then go ahead and set it.
1122   if (typeof this.playlist == 'object') {
1123     this.set(this.playlist, loadIndex);
1124     if (this.playlist.endpoint) {
1125       this.playlist = this.options.playlist = this.playlist.endpoint;
1126     }
1127     return true;
1128   }
1129 
1130   // Get the highest priority parser.
1131   var parser = osmplayer.parser['default'];
1132   for (var name in osmplayer.parser) {
1133     if (osmplayer.parser.hasOwnProperty(name)) {
1134       if (osmplayer.parser[name].valid(this.playlist)) {
1135         if (osmplayer.parser[name].priority > parser.priority) {
1136           parser = osmplayer.parser[name];
1137         }
1138       }
1139     }
1140   }
1141 
1142   // The start index.
1143   var start = this.page * this.options.pageLimit;
1144 
1145   // Get the feed from the parser.
1146   var feed = parser.getFeed(
1147     this.playlist,
1148     start,
1149     this.options.pageLimit
1150   );
1151 
1152   // Build our request.
1153   var request = {
1154     type: 'GET',
1155     url: feed,
1156     success: (function(playlist) {
1157       return function(data) {
1158         playlist.set(parser.parse(data), loadIndex);
1159       };
1160     })(this),
1161     error: (function(playlist) {
1162       return function(XMLHttpRequest, textStatus, errorThrown) {
1163         if (playlist.elements.playlist_busy) {
1164           playlist.elements.playlist_busy.hide();
1165         }
1166         playlist.trigger('error', textStatus);
1167       };
1168     })(this)
1169   };
1170 
1171   // Set the data if applicable.
1172   var dataType = parser.getType();
1173   if (dataType) {
1174     request.dataType = dataType;
1175   }
1176 
1177   // Perform an ajax callback.
1178   jQuery.ajax(request);
1179 
1180   // Return that we did something.
1181   return true;
1182 };
1183