1/* 2 * Playlist Object for the jPlayer Plugin 3 * http://www.jplayer.org 4 * 5 * Copyright (c) 2009 - 2011 Happyworm Ltd 6 * Dual licensed under the MIT and GPL licenses. 7 * - http://www.opensource.org/licenses/mit-license.php 8 * - http://www.gnu.org/copyleft/gpl.html 9 * 10 * Author: Mark J Panaghiston 11 * Version: 2.1.0 (jPlayer 2.1.0) 12 * Date: 1st September 2011 13 */ 14 15/* Code verified using http://www.jshint.com/ */ 16/*jshint asi:false, bitwise:false, boss:false, browser:true, curly:true, debug:false, eqeqeq:true, eqnull:false, evil:false, forin:false, immed:false, jquery:true, laxbreak:false, newcap:true, noarg:true, noempty:true, nonew:true, nomem:false, onevar:false, passfail:false, plusplus:false, regexp:false, undef:true, sub:false, strict:false, white:false */ 17/*global jPlayerPlaylist: true, jQuery:false, alert:false */ 18 19(function($, undefined) { 20 21 jPlayerPlaylist = function(cssSelector, playlist, options) { 22 var self = this; 23 24 this.current = 0; 25 this.loop = false; // Flag used with the jPlayer repeat event 26 this.shuffled = false; 27 this.removing = false; // Flag is true during remove animation, disabling the remove() method until complete. 28 29 this.cssSelector = $.extend({}, this._cssSelector, cssSelector); // Object: Containing the css selectors for jPlayer and its cssSelectorAncestor 30 this.options = $.extend(true, {}, this._options, options); // Object: The jPlayer constructor options for this playlist and the playlist options 31 32 this.playlist = []; // Array of Objects: The current playlist displayed (Un-shuffled or Shuffled) 33 this.original = []; // Array of Objects: The original playlist 34 35 this._initPlaylist(playlist); // Copies playlist to this.original. Then mirrors this.original to this.playlist. Creating two arrays, where the element pointers match. (Enables pointer comparison.) 36 37 // Setup the css selectors for the extra interface items used by the playlist. 38 this.cssSelector.title = this.cssSelector.cssSelectorAncestor + " .jp-title"; // Note that the text is written to the decendant li node. 39 this.cssSelector.playlist = this.cssSelector.cssSelectorAncestor + " .jp-playlist"; 40 this.cssSelector.next = this.cssSelector.cssSelectorAncestor + " .jp-next"; 41 this.cssSelector.previous = this.cssSelector.cssSelectorAncestor + " .jp-previous"; 42 this.cssSelector.shuffle = this.cssSelector.cssSelectorAncestor + " .jp-shuffle"; 43 this.cssSelector.shuffleOff = this.cssSelector.cssSelectorAncestor + " .jp-shuffle-off"; 44 45 // Override the cssSelectorAncestor given in options 46 this.options.cssSelectorAncestor = this.cssSelector.cssSelectorAncestor; 47 48 // Override the default repeat event handler 49 this.options.repeat = function(event) { 50 self.loop = event.jPlayer.options.loop; 51 }; 52 53 // Create a ready event handler to initialize the playlist 54 $(this.cssSelector.jPlayer).bind($.jPlayer.event.ready, function(event) { 55 self._init(); 56 }); 57 58 // Create an ended event handler to move to the next item 59 $(this.cssSelector.jPlayer).bind($.jPlayer.event.ended, function(event) { 60 self.next(); 61 }); 62 63 // Create a play event handler to pause other instances 64 $(this.cssSelector.jPlayer).bind($.jPlayer.event.play, function(event) { 65 $(this).jPlayer("pauseOthers"); 66 }); 67 68 // Create a resize event handler to show the title in full screen mode. 69 $(this.cssSelector.jPlayer).bind($.jPlayer.event.resize, function(event) { 70 if(event.jPlayer.options.fullScreen) { 71 $(self.cssSelector.title).show(); 72 } else { 73 $(self.cssSelector.title).hide(); 74 } 75 }); 76 77 // Create click handlers for the extra buttons that do playlist functions. 78 $(this.cssSelector.previous).click(function() { 79 self.previous(); 80 $(this).blur(); 81 return false; 82 }); 83 84 $(this.cssSelector.next).click(function() { 85 self.next(); 86 $(this).blur(); 87 return false; 88 }); 89 90 $(this.cssSelector.shuffle).click(function() { 91 self.shuffle(true); 92 return false; 93 }); 94 $(this.cssSelector.shuffleOff).click(function() { 95 self.shuffle(false); 96 return false; 97 }).hide(); 98 99 // Put the title in its initial display state 100 if(!this.options.fullScreen) { 101 $(this.cssSelector.title).hide(); 102 } 103 104 // Remove the empty <li> from the page HTML. Allows page to be valid HTML, while not interfereing with display animations 105 $(this.cssSelector.playlist + " ul").empty(); 106 107 // Create .live() handlers for the playlist items along with the free media and remove controls. 108 this._createItemHandlers(); 109 110 // Instance jPlayer 111 $(this.cssSelector.jPlayer).jPlayer(this.options); 112 }; 113 114 jPlayerPlaylist.prototype = { 115 _cssSelector: { // static object, instanced in constructor 116 jPlayer: "#jquery_jplayer_1", 117 cssSelectorAncestor: "#jp_container_1" 118 }, 119 _options: { // static object, instanced in constructor 120 playlistOptions: {
vendor: 4,459 bytes, lines 121-260
121 autoPlay: false, 122 loopOnPrevious: false, 123 shuffleOnLoop: true, 124 enableRemoveControls: false, 125 displayTime: 'slow', 126 addTime: 'fast', 127 removeTime: 'fast', 128 shuffleTime: 'slow', 129 itemClass: "jp-playlist-item", 130 freeGroupClass: "jp-free-media", 131 freeItemClass: "jp-playlist-item-free", 132 removeItemClass: "jp-playlist-item-remove" 133 } 134 }, 135 option: function(option, value) { // For changing playlist options only 136 if(value === undefined) { 137 return this.options.playlistOptions[option]; 138 } 139 140 this.options.playlistOptions[option] = value; 141 142 switch(option) { 143 case "enableRemoveControls": 144 this._updateControls(); 145 break; 146 case "itemClass": 147 case "freeGroupClass": 148 case "freeItemClass": 149 case "removeItemClass": 150 this._refresh(true); // Instant 151 this._createItemHandlers(); 152 break; 153 } 154 return this; 155 }, 156 _init: function() { 157 var self = this; 158 this._refresh(function() { 159 if(self.options.playlistOptions.autoPlay) { 160 self.play(self.current); 161 } else { 162 self.select(self.current); 163 } 164 }); 165 }, 166 _initPlaylist: function(playlist) { 167 this.current = 0; 168 this.shuffled = false; 169 this.removing = false; 170 this.original = $.extend(true, [], playlist); // Copy the Array of Objects 171 this._originalPlaylist(); 172 }, 173 _originalPlaylist: function() { 174 var self = this; 175 this.playlist = []; 176 // Make both arrays point to the same object elements. Gives us 2 different arrays, each pointing to the same actual object. ie., Not copies of the object. 177 $.each(this.original, function(i,v) { 178 self.playlist[i] = self.original[i]; 179 }); 180 }, 181 _refresh: function(instant) { 182 /* instant: Can be undefined, true or a function. 183 * undefined -> use animation timings 184 * true -> no animation 185 * function -> use animation timings and excute function at half way point. 186 */ 187 var self = this; 188 189 if(instant && !$.isFunction(instant)) { 190 $(this.cssSelector.playlist + " ul").empty(); 191 $.each(this.playlist, function(i,v) { 192 $(self.cssSelector.playlist + " ul").append(self._createListItem(self.playlist[i])); 193 }); 194 this._updateControls(); 195 } else { 196 var displayTime = $(this.cssSelector.playlist + " ul").children().length ? this.options.playlistOptions.displayTime : 0; 197 198 $(this.cssSelector.playlist + " ul").slideUp(displayTime, function() { 199 var $this = $(this); 200 $(this).empty(); 201 202 $.each(self.playlist, function(i,v) { 203 $this.append(self._createListItem(self.playlist[i])); 204 }); 205 self._updateControls(); 206 if($.isFunction(instant)) { 207 instant(); 208 } 209 if(self.playlist.length) { 210 $(this).slideDown(self.options.playlistOptions.displayTime); 211 } else { 212 $(this).show(); 213 } 214 }); 215 } 216 }, 217 _createListItem: function(media) { 218 var self = this; 219 220 // Wrap the <li> contents in a <div> 221 var listItem = "<li><div>"; 222 223 // Create remove control 224 listItem += "<a href='javascript:;' class='" + this.options.playlistOptions.removeItemClass + "'>×</a>"; 225 226 // Create links to free media 227 if(media.free) { 228 var first = true; 229 listItem += "<span class='" + this.options.playlistOptions.freeGroupClass + "'>("; 230 $.each(media, function(property,value) { 231 if($.jPlayer.prototype.format[property]) { // Check property is a media format. 232 if(first) { 233 first = false; 234 } else { 235 listItem += " | "; 236 } 237 listItem += "<a class='" + self.options.playlistOptions.freeItemClass + "' href='" + value + "' tabindex='1'>" + property + "</a>"; 238 } 239 }); 240 listItem += ")</span>"; 241 } 242 243 // The title is given next in the HTML otherwise the float:right on the free media corrupts in IE6/7 244 listItem += "<a href='javascript:;' class='" + this.options.playlistOptions.itemClass + "' tabindex='1'>" + media.title + (media.artist ? " <span class='jp-artist'>by " + media.artist + "</span>" : "") + "</a>"; 245 listItem += "</div></li>"; 246 247 return listItem; 248 }, 249 _createItemHandlers: function() { 250 var self = this; 251 // Create .live() handlers for the playlist items 252 $(this.cssSelector.playlist + " a." + this.options.playlistOptions.itemClass).die("click").live("click", function() { 253 var index = $(this).parent().parent().index(); 254 if(self.current !== index) { 255 self.play(index); 256 } else { 257 $(self.cssSelector.jPlayer).jPlayer("play"); 258 } 259 $(this).blur(); 260 return false;
261 }); 262 263 // Create .live() handlers that disable free media links to force access via right click 264 $(self.cssSelector.playlist + " a." + this.options.playlistOptions.freeItemClass).die("click").live("click", function() { 265 $(this).parent().parent().find("." + self.options.playlistOptions.itemClass).click(); 266 $(this).blur(); 267 return false; 268 }); 269 270 // Create .live() handlers for the remove controls 271 $(self.cssSelector.playlist + " a." + this.options.playlistOptions.removeItemClass).die("click").live("click", function() { 272 var index = $(this).parent().parent().index(); 273 self.remove(index); 274 $(this).blur(); 275 return false; 276 }); 277 }, 278 _updateControls: function() { 279 if(this.options.playlistOptions.enableRemoveControls) { 280 $(this.cssSelector.playlist + " ." + this.options.playlistOptions.removeItemClass).show(); 281 } else { 282 $(this.cssSelector.playlist + " ." + this.options.playlistOptions.removeItemClass).hide(); 283 } 284 if(this.shuffled) { 285 $(this.cssSelector.shuffleOff).show(); 286 $(this.cssSelector.shuffle).hide(); 287 } else { 288 $(this.cssSelector.shuffleOff).hide(); 289 $(this.cssSelector.shuffle).show(); 290 } 291 }, 292 _highlight: function(index) { 293 if(this.playlist.length && index !== undefined) { 294 $(this.cssSelector.playlist + " .jp-playlist-current").removeClass("jp-playlist-current"); 295 $(this.cssSelector.playlist + " li:nth-child(" + (index + 1) + ")").addClass("jp-playlist-current").find(".jp-playlist-item").addClass("jp-playlist-current"); 296 $(this.cssSelector.title + " li").html(this.playlist[index].title + (this.playlist[index].artist ? " <span class='jp-artist'>by " + this.playlist[index].artist + "</span>" : "")); 297 } 298 }, 299 setPlaylist: function(playlist) { 300 this._initPlaylist(playlist); 301 this._init(); 302 }, 303 add: function(media, playNow) { 304 $(this.cssSelector.playlist + " ul").append(this._createListItem(media)).find("li:last-child").hide().slideDown(this.options.playlistOptions.addTime); 305 this._updateControls(); 306 this.original.push(media); 307 this.playlist.push(media); // Both array elements share the same object pointer. Comforms with _initPlaylist(p) system. 308 309 if(playNow) { 310 this.play(this.playlist.length - 1); 311 } else { 312 if(this.original.length === 1) { 313 this.select(0); 314 } 315 } 316 }, 317 remove: function(index) { 318 var self = this; 319 320 if(index === undefined) { 321 this._initPlaylist([]); 322 this._refresh(function() { 323 $(self.cssSelector.jPlayer).jPlayer("clearMedia"); 324 }); 325 return true; 326 } else { 327 328 if(this.removing) { 329 return false; 330 } else { 331 index = (index < 0) ? self.original.length + index : index; // Negative index relates to end of array. 332 if(0 <= index && index < this.playlist.length) { 333 this.removing = true; 334 335 $(this.cssSelector.playlist + " li:nth-child(" + (index + 1) + ")").slide
vendor: 3,457 bytes, lines 335-452
335Up(this.options.playlistOptions.removeTime, function() { 336 $(this).remove(); 337 338 if(self.shuffled) { 339 var item = self.playlist[index]; 340 $.each(self.original, function(i,v) { 341 if(self.original[i] === item) { 342 self.original.splice(i, 1); 343 return false; // Exit $.each 344 } 345 }); 346 self.playlist.splice(index, 1); 347 } else { 348 self.original.splice(index, 1); 349 self.playlist.splice(index, 1); 350 } 351 352 if(self.original.length) { 353 if(index === self.current) { 354 self.current = (index < self.original.length) ? self.current : self.original.length - 1; // To cope when last element being selected when it was removed 355 self.select(self.current); 356 } else if(index < self.current) { 357 self.current--; 358 } 359 } else { 360 $(self.cssSelector.jPlayer).jPlayer("clearMedia"); 361 self.current = 0; 362 self.shuffled = false; 363 self._updateControls(); 364 } 365 366 self.removing = false; 367 }); 368 } 369 return true; 370 } 371 } 372 }, 373 select: function(index) { 374 index = (index < 0) ? this.original.length + index : index; // Negative index relates to end of array. 375 if(0 <= index && index < this.playlist.length) { 376 this.current = index; 377 this._highlight(index); 378 $(this.cssSelector.jPlayer).jPlayer("setMedia", this.playlist[this.current]); 379 } else { 380 this.current = 0; 381 } 382 }, 383 play: function(index) { 384 index = (index < 0) ? this.original.length + index : index; // Negative index relates to end of array. 385 if(0 <= index && index < this.playlist.length) { 386 if(this.playlist.length) { 387 this.select(index); 388 $(this.cssSelector.jPlayer).jPlayer("play"); 389 } 390 } else if(index === undefined) { 391 $(this.cssSelector.jPlayer).jPlayer("play"); 392 } 393 }, 394 pause: function() { 395 $(this.cssSelector.jPlayer).jPlayer("pause"); 396 }, 397 next: function() { 398 var index = (this.current + 1 < this.playlist.length) ? this.current + 1 : 0; 399 400 if(this.loop) { 401 // See if we need to shuffle before looping to start, and only shuffle if more than 1 item. 402 if(index === 0 && this.shuffled && this.options.playlistOptions.shuffleOnLoop && this.playlist.length > 1) { 403 this.shuffle(true, true); // playNow 404 } else { 405 this.play(index); 406 } 407 } else { 408 // The index will be zero if it just looped round 409 if(index > 0) { 410 this.play(index); 411 } 412 } 413 }, 414 previous: function() { 415 var index = (this.current - 1 >= 0) ? this.current - 1 : this.playlist.length - 1; 416 417 if(this.loop && this.options.playlistOptions.loopOnPrevious || index < this.playlist.length - 1) { 418 this.play(index); 419 } 420 }, 421 shuffle: function(shuffled, playNow) { 422 var self = this; 423 424 if(shuffled === undefined) { 425 shuffled = !this.shuffled; 426 } 427 428 if(shuffled || shuffled !== this.shuffled) { 429 430 $(this.cssSelector.playlist + " ul").slideUp(this.options.playlistOptions.shuffleTime, function() { 431 self.shuffled = shuffled; 432 if(shuffled) { 433 self.playlist.sort(function() { 434 return 0.5 - Math.random(); 435 }); 436 } else { 437 self._originalPlaylist(); 438 } 439 self._refresh(true); // Instant 440 441 if(playNow || !$(self.cssSelector.jPlayer).data("jPlayer").status.paused) { 442 self.play(0); 443 } else { 444 self.select(0); 445 } 446 447 $(this).slideDown(self.options.playlistOptions.shuffleTime); 448 }); 449 } 450 } 451 }; 452})(jQuery);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.