PageRenderTime 86ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/ajax/libs/jplayer/2.3.9/add-on/jplayer.playlist.js

https://gitlab.com/Mirros/cdnjs
JavaScript | 471 lines | 376 code | 48 blank | 47 comment | 68 complexity | 9a126d9a20b745188ce6f9b6a21d9517 MD5 | raw file
  1. /*
  2. * Playlist Object for the jPlayer Plugin
  3. * http://www.jplayer.org
  4. *
  5. * Copyright (c) 2009 - 2013 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.3.0
  12. * Date: 20th April 2013
  13. *
  14. * Requires:
  15. * - jQuery 1.7.0+
  16. * - jPlayer 2.3.0+
  17. */
  18. /* Code verified using http://www.jshint.com/ */
  19. /*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, onevar:false, passfail:false, plusplus:false, regexp:false, undef:true, sub:false, strict:false, white:false, smarttabs:true */
  20. /*global jPlayerPlaylist:true */
  21. (function($, undefined) {
  22. jPlayerPlaylist = function(cssSelector, playlist, options) {
  23. var self = this;
  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. this.cssSelector = $.extend({}, this._cssSelector, cssSelector); // Object: Containing the css selectors for jPlayer and its cssSelectorAncestor
  29. this.options = $.extend(true, {
  30. keyBindings: {
  31. next: {
  32. key: 39, // RIGHT
  33. fn: function() {
  34. self.next();
  35. }
  36. },
  37. previous: {
  38. key: 37, // LEFT
  39. fn: function() {
  40. self.previous();
  41. }
  42. }
  43. }
  44. }, this._options, options); // Object: The jPlayer constructor options for this playlist and the playlist options
  45. this.playlist = []; // Array of Objects: The current playlist displayed (Un-shuffled or Shuffled)
  46. this.original = []; // Array of Objects: The original playlist
  47. 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.)
  48. // Setup the css selectors for the extra interface items used by the playlist.
  49. this.cssSelector.title = this.cssSelector.cssSelectorAncestor + " .jp-title"; // Note that the text is written to the decendant li node.
  50. this.cssSelector.playlist = this.cssSelector.cssSelectorAncestor + " .jp-playlist";
  51. this.cssSelector.next = this.cssSelector.cssSelectorAncestor + " .jp-next";
  52. this.cssSelector.previous = this.cssSelector.cssSelectorAncestor + " .jp-previous";
  53. this.cssSelector.shuffle = this.cssSelector.cssSelectorAncestor + " .jp-shuffle";
  54. this.cssSelector.shuffleOff = this.cssSelector.cssSelectorAncestor + " .jp-shuffle-off";
  55. // Override the cssSelectorAncestor given in options
  56. this.options.cssSelectorAncestor = this.cssSelector.cssSelectorAncestor;
  57. // Override the default repeat event handler
  58. this.options.repeat = function(event) {
  59. self.loop = event.jPlayer.options.loop;
  60. };
  61. // Create a ready event handler to initialize the playlist
  62. $(this.cssSelector.jPlayer).bind($.jPlayer.event.ready, function() {
  63. self._init();
  64. });
  65. // Create an ended event handler to move to the next item
  66. $(this.cssSelector.jPlayer).bind($.jPlayer.event.ended, function() {
  67. self.next();
  68. });
  69. // Create a play event handler to pause other instances
  70. $(this.cssSelector.jPlayer).bind($.jPlayer.event.play, function() {
  71. $(this).jPlayer("pauseOthers");
  72. });
  73. // Create a resize event handler to show the title in full screen mode.
  74. $(this.cssSelector.jPlayer).bind($.jPlayer.event.resize, function(event) {
  75. if(event.jPlayer.options.fullScreen) {
  76. $(self.cssSelector.title).show();
  77. } else {
  78. $(self.cssSelector.title).hide();
  79. }
  80. });
  81. // Create click handlers for the extra buttons that do playlist functions.
  82. $(this.cssSelector.previous).click(function() {
  83. self.previous();
  84. $(this).blur();
  85. return false;
  86. });
  87. $(this.cssSelector.next).click(function() {
  88. self.next();
  89. $(this).blur();
  90. return false;
  91. });
  92. $(this.cssSelector.shuffle).click(function() {
  93. self.shuffle(true);
  94. return false;
  95. });
  96. $(this.cssSelector.shuffleOff).click(function() {
  97. self.shuffle(false);
  98. return false;
  99. }).hide();
  100. // Put the title in its initial display state
  101. if(!this.options.fullScreen) {
  102. $(this.cssSelector.title).hide();
  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. // Create .on() handlers for the playlist items along with the free media and remove controls.
  107. this._createItemHandlers();
  108. // Instance jPlayer
  109. $(this.cssSelector.jPlayer).jPlayer(this.options);
  110. };
  111. jPlayerPlaylist.prototype = {
  112. _cssSelector: { // static object, instanced in constructor
  113. jPlayer: "#jquery_jplayer_1",
  114. cssSelectorAncestor: "#jp_container_1"
  115. },
  116. _options: { // static object, instanced in constructor
  117. playlistOptions: {
  118. autoPlay: false,
  119. loopOnPrevious: false,
  120. shuffleOnLoop: true,
  121. enableRemoveControls: false,
  122. displayTime: 'slow',
  123. addTime: 'fast',
  124. removeTime: 'fast',
  125. shuffleTime: 'slow',
  126. itemClass: "jp-playlist-item",
  127. freeGroupClass: "jp-free-media",
  128. freeItemClass: "jp-playlist-item-free",
  129. removeItemClass: "jp-playlist-item-remove"
  130. }
  131. },
  132. option: function(option, value) { // For changing playlist options only
  133. if(value === undefined) {
  134. return this.options.playlistOptions[option];
  135. }
  136. this.options.playlistOptions[option] = value;
  137. switch(option) {
  138. case "enableRemoveControls":
  139. this._updateControls();
  140. break;
  141. case "itemClass":
  142. case "freeGroupClass":
  143. case "freeItemClass":
  144. case "removeItemClass":
  145. this._refresh(true); // Instant
  146. this._createItemHandlers();
  147. break;
  148. }
  149. return this;
  150. },
  151. _init: function() {
  152. var self = this;
  153. this._refresh(function() {
  154. if(self.options.playlistOptions.autoPlay) {
  155. self.play(self.current);
  156. } else {
  157. self.select(self.current);
  158. }
  159. });
  160. },
  161. _initPlaylist: function(playlist) {
  162. this.current = 0;
  163. this.shuffled = false;
  164. this.removing = false;
  165. this.original = $.extend(true, [], playlist); // Copy the Array of Objects
  166. this._originalPlaylist();
  167. },
  168. _originalPlaylist: function() {
  169. var self = this;
  170. this.playlist = [];
  171. // 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.
  172. $.each(this.original, function(i) {
  173. self.playlist[i] = self.original[i];
  174. });
  175. },
  176. _refresh: function(instant) {
  177. /* instant: Can be undefined, true or a function.
  178. * undefined -> use animation timings
  179. * true -> no animation
  180. * function -> use animation timings and excute function at half way point.
  181. */
  182. var self = this;
  183. if(instant && !$.isFunction(instant)) {
  184. $(this.cssSelector.playlist + " ul").empty();
  185. $.each(this.playlist, function(i) {
  186. $(self.cssSelector.playlist + " ul").append(self._createListItem(self.playlist[i]));
  187. });
  188. this._updateControls();
  189. } else {
  190. var displayTime = $(this.cssSelector.playlist + " ul").children().length ? this.options.playlistOptions.displayTime : 0;
  191. $(this.cssSelector.playlist + " ul").slideUp(displayTime, function() {
  192. var $this = $(this);
  193. $(this).empty();
  194. $.each(self.playlist, function(i) {
  195. $this.append(self._createListItem(self.playlist[i]));
  196. });
  197. self._updateControls();
  198. if($.isFunction(instant)) {
  199. instant();
  200. }
  201. if(self.playlist.length) {
  202. $(this).slideDown(self.options.playlistOptions.displayTime);
  203. } else {
  204. $(this).show();
  205. }
  206. });
  207. }
  208. },
  209. _createListItem: function(media) {
  210. var self = this;
  211. // Wrap the <li> contents in a <div>
  212. var listItem = "<li><div>";
  213. // Create remove control
  214. listItem += "<a href='javascript:;' class='" + this.options.playlistOptions.removeItemClass + "'>&times;</a>";
  215. // Create links to free media
  216. if(media.free) {
  217. var first = true;
  218. listItem += "<span class='" + this.options.playlistOptions.freeGroupClass + "'>(";
  219. $.each(media, function(property,value) {
  220. if($.jPlayer.prototype.format[property]) { // Check property is a media format.
  221. if(first) {
  222. first = false;
  223. } else {
  224. listItem += " | ";
  225. }
  226. listItem += "<a class='" + self.options.playlistOptions.freeItemClass + "' href='" + value + "' tabindex='1'>" + property + "</a>";
  227. }
  228. });
  229. listItem += ")</span>";
  230. }
  231. // The title is given next in the HTML otherwise the float:right on the free media corrupts in IE6/7
  232. listItem += "<a href='javascript:;' class='" + this.options.playlistOptions.itemClass + "' tabindex='1'>" + media.title + (media.artist ? " <span class='jp-artist'>by " + media.artist + "</span>" : "") + "</a>";
  233. listItem += "</div></li>";
  234. return listItem;
  235. },
  236. _createItemHandlers: function() {
  237. var self = this;
  238. // Create live handlers for the playlist items
  239. $(this.cssSelector.playlist).off("click", "a." + this.options.playlistOptions.itemClass).on("click", "a." + this.options.playlistOptions.itemClass, function() {
  240. var index = $(this).parent().parent().index();
  241. if(self.current !== index) {
  242. self.play(index);
  243. } else {
  244. $(self.cssSelector.jPlayer).jPlayer("play");
  245. }
  246. $(this).blur();
  247. return false;
  248. });
  249. // Create live handlers that disable free media links to force access via right click
  250. $(this.cssSelector.playlist).off("click", "a." + this.options.playlistOptions.freeItemClass).on("click", "a." + this.options.playlistOptions.freeItemClass, function() {
  251. $(this).parent().parent().find("." + self.options.playlistOptions.itemClass).click();
  252. $(this).blur();
  253. return false;
  254. });
  255. // Create live handlers for the remove controls
  256. $(this.cssSelector.playlist).off("click", "a." + this.options.playlistOptions.removeItemClass).on("click", "a." + this.options.playlistOptions.removeItemClass, function() {
  257. var index = $(this).parent().parent().index();
  258. self.remove(index);
  259. $(this).blur();
  260. return false;
  261. });
  262. },
  263. _updateControls: function() {
  264. if(this.options.playlistOptions.enableRemoveControls) {
  265. $(this.cssSelector.playlist + " ." + this.options.playlistOptions.removeItemClass).show();
  266. } else {
  267. $(this.cssSelector.playlist + " ." + this.options.playlistOptions.removeItemClass).hide();
  268. }
  269. if(this.shuffled) {
  270. $(this.cssSelector.shuffleOff).show();
  271. $(this.cssSelector.shuffle).hide();
  272. } else {
  273. $(this.cssSelector.shuffleOff).hide();
  274. $(this.cssSelector.shuffle).show();
  275. }
  276. },
  277. _highlight: function(index) {
  278. if(this.playlist.length && index !== undefined) {
  279. $(this.cssSelector.playlist + " .jp-playlist-current").removeClass("jp-playlist-current");
  280. $(this.cssSelector.playlist + " li:nth-child(" + (index + 1) + ")").addClass("jp-playlist-current").find(".jp-playlist-item").addClass("jp-playlist-current");
  281. $(this.cssSelector.title + " li").html(this.playlist[index].title + (this.playlist[index].artist ? " <span class='jp-artist'>by " + this.playlist[index].artist + "</span>" : ""));
  282. }
  283. },
  284. setPlaylist: function(playlist) {
  285. this._initPlaylist(playlist);
  286. this._init();
  287. },
  288. add: function(media, playNow) {
  289. $(this.cssSelector.playlist + " ul").append(this._createListItem(media)).find("li:last-child").hide().slideDown(this.options.playlistOptions.addTime);
  290. this._updateControls();
  291. this.original.push(media);
  292. this.playlist.push(media); // Both array elements share the same object pointer. Comforms with _initPlaylist(p) system.
  293. if(playNow) {
  294. this.play(this.playlist.length - 1);
  295. } else {
  296. if(this.original.length === 1) {
  297. this.select(0);
  298. }
  299. }
  300. },
  301. remove: function(index) {
  302. var self = this;
  303. if(index === undefined) {
  304. this._initPlaylist([]);
  305. this._refresh(function() {
  306. $(self.cssSelector.jPlayer).jPlayer("clearMedia");
  307. });
  308. return true;
  309. } else {
  310. if(this.removing) {
  311. return false;
  312. } else {
  313. index = (index < 0) ? self.original.length + index : index; // Negative index relates to end of array.
  314. if(0 <= index && index < this.playlist.length) {
  315. this.removing = true;
  316. $(this.cssSelector.playlist + " li:nth-child(" + (index + 1) + ")").slideUp(this.options.playlistOptions.removeTime, function() {
  317. $(this).remove();
  318. if(self.shuffled) {
  319. var item = self.playlist[index];
  320. $.each(self.original, function(i) {
  321. if(self.original[i] === item) {
  322. self.original.splice(i, 1);
  323. return false; // Exit $.each
  324. }
  325. });
  326. self.playlist.splice(index, 1);
  327. } else {
  328. self.original.splice(index, 1);
  329. self.playlist.splice(index, 1);
  330. }
  331. if(self.original.length) {
  332. if(index === self.current) {
  333. self.current = (index < self.original.length) ? self.current : self.original.length - 1; // To cope when last element being selected when it was removed
  334. self.select(self.current);
  335. } else if(index < self.current) {
  336. self.current--;
  337. }
  338. } else {
  339. $(self.cssSelector.jPlayer).jPlayer("clearMedia");
  340. self.current = 0;
  341. self.shuffled = false;
  342. self._updateControls();
  343. }
  344. self.removing = false;
  345. });
  346. }
  347. return true;
  348. }
  349. }
  350. },
  351. select: function(index) {
  352. index = (index < 0) ? this.original.length + index : index; // Negative index relates to end of array.
  353. if(0 <= index && index < this.playlist.length) {
  354. this.current = index;
  355. this._highlight(index);
  356. $(this.cssSelector.jPlayer).jPlayer("setMedia", this.playlist[this.current]);
  357. } else {
  358. this.current = 0;
  359. }
  360. },
  361. play: function(index) {
  362. index = (index < 0) ? this.original.length + index : index; // Negative index relates to end of array.
  363. if(0 <= index && index < this.playlist.length) {
  364. if(this.playlist.length) {
  365. this.select(index);
  366. $(this.cssSelector.jPlayer).jPlayer("play");
  367. }
  368. } else if(index === undefined) {
  369. $(this.cssSelector.jPlayer).jPlayer("play");
  370. }
  371. },
  372. pause: function() {
  373. $(this.cssSelector.jPlayer).jPlayer("pause");
  374. },
  375. next: function() {
  376. var index = (this.current + 1 < this.playlist.length) ? this.current + 1 : 0;
  377. if(this.loop) {
  378. // See if we need to shuffle before looping to start, and only shuffle if more than 1 item.
  379. if(index === 0 && this.shuffled && this.options.playlistOptions.shuffleOnLoop && this.playlist.length > 1) {
  380. this.shuffle(true, true); // playNow
  381. } else {
  382. this.play(index);
  383. }
  384. } else {
  385. // The index will be zero if it just looped round
  386. if(index > 0) {
  387. this.play(index);
  388. }
  389. }
  390. },
  391. previous: function() {
  392. var index = (this.current - 1 >= 0) ? this.current - 1 : this.playlist.length - 1;
  393. if(this.loop && this.options.playlistOptions.loopOnPrevious || index < this.playlist.length - 1) {
  394. this.play(index);
  395. }
  396. },
  397. shuffle: function(shuffled, playNow) {
  398. var self = this;
  399. if(shuffled === undefined) {
  400. shuffled = !this.shuffled;
  401. }
  402. if(shuffled || shuffled !== this.shuffled) {
  403. $(this.cssSelector.playlist + " ul").slideUp(this.options.playlistOptions.shuffleTime, function() {
  404. self.shuffled = shuffled;
  405. if(shuffled) {
  406. self.playlist.sort(function() {
  407. return 0.5 - Math.random();
  408. });
  409. } else {
  410. self._originalPlaylist();
  411. }
  412. self._refresh(true); // Instant
  413. if(playNow || !$(self.cssSelector.jPlayer).data("jPlayer").status.paused) {
  414. self.play(0);
  415. } else {
  416. self.select(0);
  417. }
  418. $(this).slideDown(self.options.playlistOptions.shuffleTime);
  419. });
  420. }
  421. }
  422. };
  423. })(jQuery);