/** * @constructor * @extends minplayer.display * @class This class creates the playlist functionality for the minplayer. * * @param {object} context The jQuery context. * @param {object} options This components options. */ osmplayer.playlist = function(context, options) { // Derive from display minplayer.display.call(this, 'playlist', context, options); }; /** Derive from minplayer.display. */ osmplayer.playlist.prototype = new minplayer.display(); /** Reset the constructor. */ osmplayer.playlist.prototype.constructor = osmplayer.playlist; /** * Returns the default options for this plugin. * * @param {object} options The default options for this plugin. */ osmplayer.playlist.prototype.defaultOptions = function(options) { options.vertical = true; options.playlist = ''; options.pageLimit = 10; options.autoNext = true; options.shuffle = false; options.loop = false; options.hysteresis = 40; options.scrollSpeed = 20; options.scrollMode = 'auto'; minplayer.display.prototype.defaultOptions.call(this, options); }; /** * @see minplayer.plugin#construct */ osmplayer.playlist.prototype.construct = function() { /** The nodes within this playlist. */ this.nodes = []; // Current page. this.page = -1; // The total amount of nodes. this.totalItems = 0; // The current loaded item index. this.currentItem = -1; // The play playqueue. this.playqueue = []; // The playqueue position. this.playqueuepos = 0; // The current playlist. this.playlist = this.options.playlist; // Create the scroll bar. this.scroll = null; // Create our orientation variable. this.orient = { pos: this.options.vertical ? 'y' : 'x', pagePos: this.options.vertical ? 'pageY' : 'pageX', offset: this.options.vertical ? 'top' : 'left', wrapperSize: this.options.vertical ? 'wrapperH' : 'wrapperW', minScroll: this.options.vertical ? 'minScrollY' : 'minScrollX', maxScroll: this.options.vertical ? 'maxScrollY' : 'maxScrollX', size: this.options.vertical ? 'height' : 'width' }; // Create the pager. this.pager = this.create('pager', 'osmplayer'); this.pager.ubind(this.uuid + ':nextPage', (function(playlist) { return function(event) { playlist.nextPage(); }; })(this)); this.pager.ubind(this.uuid + ':prevPage', (function(playlist) { return function(event) { playlist.prevPage(); }; })(this)); // Call the minplayer plugin constructor. minplayer.display.prototype.construct.call(this); // Load the "next" item. this.hasPlaylist = this.next(); // Say that we are ready. this.ready(); }; /** * @see minplayer.plugin.onAdded */ osmplayer.playlist.prototype.onAdded = function(plugin) { // Get the media. if (this.options.autoNext) { // Get the player from this plugin. plugin.get('player', (function(playlist) { return function(player) { player.ubind(playlist.uuid + ':player_ended', function(event) { if (playlist.hasPlaylist) { if (typeof player.options.originalAutoPlay == 'undefined') { player.options.originalAutoPlay = player.options.autoplay; } player.options.autoplay = true; playlist.next(); } }); }; })(this)); } }; /** * Wrapper around the scroll scrollTo method. * * @param {number} pos The position you would like to set the list. * @param {boolean} relative If this is a relative position change. */ osmplayer.playlist.prototype.scrollTo = function(pos, relative) { if (this.scroll) { this.scroll.options.hideScrollbar = false; if (this.options.vertical) { this.scroll.scrollTo(0, pos, 0, relative); } else { this.scroll.scrollTo(pos, 0, 0, relative); } this.scroll.options.hideScrollbar = true; } }; /** * Refresh the scrollbar. */ osmplayer.playlist.prototype.refreshScroll = function() { // Make sure that our window has the addEventListener to keep IE happy. if (!window.addEventListener) { setTimeout((function(playlist) { return function() { playlist.refreshScroll.call(playlist); }; })(this), 200); return; } // Check the size of the playlist. var list = this.elements.list; var scroll = this.elements.scroll; // Destroy the scroll bar first. if (this.scroll) { this.scroll.scrollTo(0, 0); this.scroll.destroy(); this.scroll = null; this.elements.list .unbind('mousemove') .unbind('mouseenter') .unbind('mouseleave'); } // Need to force the width of the list. if (!this.options.vertical) { var listSize = 0; jQuery.each(this.elements.list.children(), function() { listSize += jQuery(this).outerWidth(); }); this.elements.list.width(listSize); } // Check to see if we should add a scroll bar functionality. if ((list.length > 0) && (scroll.length > 0) && (list[this.orient.size]() > scroll[this.orient.size]())) { // Setup the osmplayer.iScroll component. this.scroll = new osmplayer.iScroll(this.elements.scroll.eq(0)[0], { hScroll: !this.options.vertical, hScrollbar: !this.options.vertical, vScroll: this.options.vertical, vScrollbar: this.options.vertical, hideScrollbar: (this.options.scrollMode !== 'none') }); // Use autoScroll for non-touch devices. if ((this.options.scrollMode == 'auto') && !minplayer.hasTouch) { // Bind to the mouse events for autoscrolling. this.elements.list.bind('mousemove', (function(playlist) { return function(event) { event.preventDefault(); var offset = playlist.display.offset()[playlist.orient.offset]; playlist.mousePos = event[playlist.orient.pagePos]; playlist.mousePos -= offset; }; })(this)).bind('mouseenter', (function(playlist) { return function(event) { event.preventDefault(); playlist.scrolling = true; var setScroll = function() { if (playlist.scrolling) { var scrollSize = playlist.scroll[playlist.orient.wrapperSize]; var scrollMid = (scrollSize / 2); var delta = playlist.mousePos - scrollMid; if (Math.abs(delta) > playlist.options.hysteresis) { var hyst = playlist.options.hysteresis; hyst *= (delta > 0) ? -1 : 0; delta = (playlist.options.scrollSpeed * (delta + hyst)); delta /= scrollMid; var pos = playlist.scroll[playlist.orient.pos] - delta; var min = playlist.scroll[playlist.orient.minScroll] || 0; var max = playlist.scroll[playlist.orient.maxScroll]; if (pos >= min) { playlist.scrollTo(min); } else if (pos <= max) { playlist.scrollTo(max); } else { playlist.scrollTo(delta, true); } } // Set timeout to try again. setTimeout(setScroll, 30); } }; setScroll(); }; })(this)).bind('mouseleave', (function(playlist) { return function(event) { event.preventDefault(); playlist.scrolling = false; }; })(this)); } this.scroll.refresh(); this.scroll.scrollTo(0, 0, 200); } }; /** * Adds a new node to the playlist. * * @param {object} node The node that you would like to add to the playlist. */ osmplayer.playlist.prototype.addNode = function(node) { // Get the current index for this node. var index = this.nodes.length; // Create the teaser object. var teaser = this.create('teaser', 'osmplayer', this.elements.list); // Set the node for this teaser. teaser.setNode(node); // Bind to when it loads. teaser.ubind(this.uuid + ':nodeLoad', (function(playlist) { return function(event, data) { playlist.loadItem(index, true); }; })(this)); // Add this to our nodes array. this.nodes.push(teaser); }; /** * Sets the playlist. * * @param {object} playlist The playlist object. * @param {integer} loadIndex The index of the item to load. */ osmplayer.playlist.prototype.set = function(playlist, loadIndex) { // Check to make sure the playlist is an object. if (typeof playlist !== 'object') { this.trigger('error', 'Playlist must be an object to set'); return; } // Check to make sure the playlist has correct format. if (!playlist.hasOwnProperty('total_rows')) { this.trigger('error', 'Unknown playlist format.'); return; } // Make sure the playlist has some rows. if (playlist.total_rows && playlist.nodes.length) { // Set the total rows. this.totalItems = playlist.total_rows; this.currentItem = 0; // Show or hide the next page if there is or is not a next page. if ((((this.page + 1) * this.options.pageLimit) >= this.totalItems) || (this.totalItems == playlist.nodes.length)) { this.pager.nextPage.hide(); } else { this.pager.nextPage.show(); } var teaser = null; var numNodes = playlist.nodes.length; this.elements.list.empty(); this.nodes = []; // Iterate through all the nodes. for (var index = 0; index < numNodes; index++) { // Add this node to the playlist. this.addNode(playlist.nodes[index]); // If the index is equal to the loadIndex. if (loadIndex === index) { this.loadItem(index); } } // Refresh the sizes. this.refreshScroll(); // Trigger that the playlist has loaded. this.trigger('playlistLoad', playlist); } // Show that we are no longer busy. if (this.elements.playlist_busy) { this.elements.playlist_busy.hide(); } }; /** * Stores the current playlist state in the playqueue. */ osmplayer.playlist.prototype.setQueue = function() { // Add this item to the playqueue. this.playqueue.push({ page: this.page, item: this.currentItem }); // Store the current playqueue position. this.playqueuepos = this.playqueue.length; }; /** * Loads the next item. * * @return {boolean} TRUE if loaded, FALSE if not. */ osmplayer.playlist.prototype.next = function() { var item = 0, page = this.page; // See if we are at the front of the playqueue. if (this.playqueuepos >= this.playqueue.length) { // If this is shuffle, then load a random item. if (this.options.shuffle) { item = Math.floor(Math.random() * this.totalItems); page = Math.floor(item / this.options.pageLimit); item = item % this.options.pageLimit; return this.load(page, item); } else { // Otherwise, increment the current item by one. item = (this.currentItem + 1); if (item >= this.nodes.length) { return this.load(page + 1, 0); } else { return this.loadItem(item); } } } else { // Load the next item in the playqueue. this.playqueuepos = this.playqueuepos + 1; var currentQueue = this.playqueue[this.playqueuepos]; return this.load(currentQueue.page, currentQueue.item); } }; /** * Loads the previous item. * * @return {boolean} TRUE if loaded, FALSE if not. */ osmplayer.playlist.prototype.prev = function() { // Move back into the playqueue. this.playqueuepos = this.playqueuepos - 1; this.playqueuepos = (this.playqueuepos < 0) ? 0 : this.playqueuepos; var currentQueue = this.playqueue[this.playqueuepos]; if (currentQueue) { return this.load(currentQueue.page, currentQueue.item); } return false; }; /** * Loads a playlist node. * * @param {number} index The index of the item you would like to load. * @return {boolean} TRUE if loaded, FALSE if not. */ osmplayer.playlist.prototype.loadItem = function(index, autoplay) { if (index < this.nodes.length) { this.setQueue(); // Get the teaser at the current index and deselect it. var teaser = this.nodes[this.currentItem]; teaser.select(false); this.currentItem = index; // Get the new teaser and select it. teaser = this.nodes[index]; teaser.select(true); teaser.node.autoplay = !!autoplay; this.trigger('nodeLoad', teaser.node); return true; } return false; }; /** * Loads the next page. * * @param {integer} loadIndex The index of the item to load. * @return {boolean} TRUE if loaded, FALSE if not. */ osmplayer.playlist.prototype.nextPage = function(loadIndex) { return this.load(this.page + 1, loadIndex); }; /** * Loads the previous page. * * @param {integer} loadIndex The index of the item to load. * @return {boolean} TRUE if loaded, FALSE if not. */ osmplayer.playlist.prototype.prevPage = function(loadIndex) { return this.load(this.page - 1, loadIndex); }; /** * Loads a playlist. * * @param {integer} page The page to load. * @param {integer} loadIndex The index of the item to load. * @return {boolean} TRUE if loaded, FALSE if not. */ osmplayer.playlist.prototype.load = function(page, loadIndex) { // If the playlist and pages are the same, then no need to load. if ((this.playlist == this.options.playlist) && (page == this.page)) { return this.loadItem(loadIndex); } // Set the new playlist. this.playlist = this.options.playlist; // Return if there aren't any playlists to play. if (!this.playlist) { return false; } // Determine if we need to loop. var maxPages = Math.floor(this.totalItems / this.options.pageLimit); if (page > maxPages) { if (this.options.loop) { page = 0; loadIndex = 0; } else { return false; } } // Say that we are busy. if (this.elements.playlist_busy) { this.elements.playlist_busy.show(); } // Normalize the page. page = page || 0; page = (page < 0) ? 0 : page; // Set the queue. this.setQueue(); // Set the new page. this.page = page; // Hide or show the page based on if we are on the first page. if (this.page === 0) { this.pager.prevPage.hide(); } else { this.pager.prevPage.show(); } // If the playlist is an object, then go ahead and set it. if (typeof this.playlist == 'object') { this.set(this.playlist, loadIndex); if (this.playlist.endpoint) { this.playlist = this.options.playlist = this.playlist.endpoint; } return true; } // Get the highest priority parser. var parser = osmplayer.parser['default']; for (var name in osmplayer.parser) { if (osmplayer.parser.hasOwnProperty(name)) { if (osmplayer.parser[name].valid(this.playlist)) { if (osmplayer.parser[name].priority > parser.priority) { parser = osmplayer.parser[name]; } } } } // The start index. var start = this.page * this.options.pageLimit; // Get the feed from the parser. var feed = parser.getFeed( this.playlist, start, this.options.pageLimit ); // Build our request. var request = { type: 'GET', url: feed, success: (function(playlist) { return function(data) { playlist.set(parser.parse(data), loadIndex); }; })(this), error: (function(playlist) { return function(XMLHttpRequest, textStatus, errorThrown) { if (playlist.elements.playlist_busy) { playlist.elements.playlist_busy.hide(); } playlist.trigger('error', textStatus); }; })(this) }; // Set the data if applicable. var dataType = parser.getType(); if (dataType) { request.dataType = dataType; } // Perform an ajax callback. jQuery.ajax(request); // Return that we did something. return true; };