circle.player.js 8.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. /*
  2. * CirclePlayer for the jPlayer Plugin (jQuery)
  3. * http://www.jplayer.org
  4. *
  5. * Copyright (c) 2009 - 2012 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. * Version: 1.0.1 (jPlayer 2.1.0+)
  11. * Date: 30th May 2011
  12. *
  13. * Author: Mark J Panaghiston @thepag
  14. *
  15. * CirclePlayer prototype developed by:
  16. * Mark Boas @maboa
  17. * Silvia Benvenuti @aulentina
  18. * Jussi Kalliokoski @quinirill
  19. *
  20. * Inspired by :
  21. * Neway @imneway http://imneway.net/ http://forrst.com/posts/Untitled-CPt
  22. * and
  23. * Liam McKay @liammckay http://dribbble.com/shots/50882-Purple-Play-Pause
  24. *
  25. * Standing on the shoulders of :
  26. * John Resig @jresig
  27. * Mark Panaghiston @thepag
  28. * Louis-Rémi Babé @Louis_Remi
  29. */
  30. var CirclePlayer = function(jPlayerSelector, media, options) {
  31. var self = this,
  32. defaults = {
  33. // solution: "flash, html", // For testing Flash with CSS3
  34. supplied: "m4a, oga",
  35. // Android 2.3 corrupts media element if preload:"none" is used.
  36. // preload: "none", // No point preloading metadata since no times are displayed. It helps keep the buffer state correct too.
  37. cssSelectorAncestor: "#cp_container_1",
  38. cssSelector: {
  39. play: ".cp-play",
  40. pause: ".cp-pause"
  41. }
  42. },
  43. cssSelector = {
  44. bufferHolder: ".cp-buffer-holder",
  45. buffer1: ".cp-buffer-1",
  46. buffer2: ".cp-buffer-2",
  47. progressHolder: ".cp-progress-holder",
  48. progress1: ".cp-progress-1",
  49. progress2: ".cp-progress-2",
  50. circleControl: ".cp-circle-control"
  51. };
  52. this.cssClass = {
  53. gt50: "cp-gt50",
  54. fallback: "cp-fallback"
  55. };
  56. this.spritePitch = 104;
  57. this.spriteRatio = 0.24; // Number of steps / 100
  58. this.player = $(jPlayerSelector);
  59. this.media = $.extend({}, media);
  60. this.options = $.extend(true, {}, defaults, options); // Deep copy
  61. this.cssTransforms = Modernizr.csstransforms;
  62. this.audio = {};
  63. this.dragging = false; // Indicates if the progressbar is being 'dragged'.
  64. this.eventNamespace = ".CirclePlayer"; // So the events can easily be removed in destroy.
  65. this.jq = {};
  66. $.each(cssSelector, function(entity, cssSel) {
  67. self.jq[entity] = $(self.options.cssSelectorAncestor + " " + cssSel);
  68. });
  69. this._initSolution();
  70. this._initPlayer();
  71. };
  72. CirclePlayer.prototype = {
  73. _createHtml: function() {
  74. },
  75. _initPlayer: function() {
  76. var self = this;
  77. this.player.jPlayer(this.options);
  78. this.player.bind($.jPlayer.event.ready + this.eventNamespace, function(event) {
  79. if(event.jPlayer.html.used && event.jPlayer.html.audio.available) {
  80. self.audio = $(this).data("jPlayer").htmlElement.audio;
  81. }
  82. $(this).jPlayer("setMedia", self.media);
  83. self._initCircleControl();
  84. });
  85. this.player.bind($.jPlayer.event.play + this.eventNamespace, function(event) {
  86. $(this).jPlayer("pauseOthers");
  87. });
  88. // This event fired as play time increments
  89. this.player.bind($.jPlayer.event.timeupdate + this.eventNamespace, function(event) {
  90. if (!self.dragging) {
  91. self._timeupdate(event.jPlayer.status.currentPercentAbsolute);
  92. }
  93. });
  94. // This event fired as buffered time increments
  95. this.player.bind($.jPlayer.event.progress + this.eventNamespace, function(event) {
  96. var percent = 0;
  97. if((typeof self.audio.buffered === "object") && (self.audio.buffered.length > 0)) {
  98. if(self.audio.duration > 0) {
  99. var bufferTime = 0;
  100. for(var i = 0; i < self.audio.buffered.length; i++) {
  101. bufferTime += self.audio.buffered.end(i) - self.audio.buffered.start(i);
  102. // console.log(i + " | start = " + self.audio.buffered.start(i) + " | end = " + self.audio.buffered.end(i) + " | bufferTime = " + bufferTime + " | duration = " + self.audio.duration);
  103. }
  104. percent = 100 * bufferTime / self.audio.duration;
  105. } // else the Metadata has not been read yet.
  106. // console.log("percent = " + percent);
  107. } else { // Fallback if buffered not supported
  108. // percent = event.jPlayer.status.seekPercent;
  109. percent = 0; // Cleans up the inital conditions on all browsers, since seekPercent defaults to 100 when object is undefined.
  110. }
  111. self._progress(percent); // Problem here at initial condition. Due to the Opera clause above of buffered.length > 0 above... Removing it means Opera's white buffer ring never shows like with polyfill.
  112. // Firefox 4 does not always give the final progress event when buffered = 100%
  113. });
  114. this.player.bind($.jPlayer.event.ended + this.eventNamespace, function(event) {
  115. self._resetSolution();
  116. });
  117. },
  118. _initSolution: function() {
  119. if (this.cssTransforms) {
  120. this.jq.progressHolder.show();
  121. this.jq.bufferHolder.show();
  122. }
  123. else {
  124. this.jq.progressHolder.addClass(this.cssClass.gt50).show();
  125. this.jq.progress1.addClass(this.cssClass.fallback);
  126. this.jq.progress2.hide();
  127. this.jq.bufferHolder.hide();
  128. }
  129. this._resetSolution();
  130. },
  131. _resetSolution: function() {
  132. if (this.cssTransforms) {
  133. this.jq.progressHolder.removeClass(this.cssClass.gt50);
  134. this.jq.progress1.css({'transform': 'rotate(0deg)'});
  135. this.jq.progress2.css({'transform': 'rotate(0deg)'}).hide();
  136. }
  137. else {
  138. this.jq.progress1.css('background-position', '0 ' + this.spritePitch + 'px');
  139. }
  140. },
  141. _initCircleControl: function() {
  142. var self = this;
  143. this.jq.circleControl.grab({
  144. onstart: function(){
  145. self.dragging = true;
  146. }, onmove: function(event){
  147. var pc = self._getArcPercent(event.position.x, event.position.y);
  148. self.player.jPlayer("playHead", pc).jPlayer("play");
  149. self._timeupdate(pc);
  150. }, onfinish: function(event){
  151. self.dragging = false;
  152. var pc = self._getArcPercent(event.position.x, event.position.y);
  153. self.player.jPlayer("playHead", pc).jPlayer("play");
  154. }
  155. });
  156. },
  157. _timeupdate: function(percent) {
  158. var degs = percent * 3.6+"deg";
  159. var spriteOffset = (Math.floor((Math.round(percent))*this.spriteRatio)-1)*-this.spritePitch;
  160. if (percent <= 50) {
  161. if (this.cssTransforms) {
  162. this.jq.progressHolder.removeClass(this.cssClass.gt50);
  163. this.jq.progress1.css({'transform': 'rotate(' + degs + ')'});
  164. this.jq.progress2.hide();
  165. } else { // fall back
  166. this.jq.progress1.css('background-position', '0 '+spriteOffset+'px');
  167. }
  168. } else if (percent <= 100) {
  169. if (this.cssTransforms) {
  170. this.jq.progressHolder.addClass(this.cssClass.gt50);
  171. this.jq.progress1.css({'transform': 'rotate(180deg)'});
  172. this.jq.progress2.css({'transform': 'rotate(' + degs + ')'});
  173. this.jq.progress2.show();
  174. } else { // fall back
  175. this.jq.progress1.css('background-position', '0 '+spriteOffset+'px');
  176. }
  177. }
  178. },
  179. _progress: function(percent) {
  180. var degs = percent * 3.6+"deg";
  181. if (this.cssTransforms) {
  182. if (percent <= 50) {
  183. this.jq.bufferHolder.removeClass(this.cssClass.gt50);
  184. this.jq.buffer1.css({'transform': 'rotate(' + degs + ')'});
  185. this.jq.buffer2.hide();
  186. } else if (percent <= 100) {
  187. this.jq.bufferHolder.addClass(this.cssClass.gt50);
  188. this.jq.buffer1.css({'transform': 'rotate(180deg)'});
  189. this.jq.buffer2.show();
  190. this.jq.buffer2.css({'transform': 'rotate(' + degs + ')'});
  191. }
  192. }
  193. },
  194. _getArcPercent: function(pageX, pageY) {
  195. var offset = this.jq.circleControl.offset(),
  196. x = pageX - offset.left - this.jq.circleControl.width()/2,
  197. y = pageY - offset.top - this.jq.circleControl.height()/2,
  198. theta = Math.atan2(y,x);
  199. if (theta > -1 * Math.PI && theta < -0.5 * Math.PI) {
  200. theta = 2 * Math.PI + theta;
  201. }
  202. // theta is now value between -0.5PI and 1.5PI
  203. // ready to be normalized and applied
  204. return (theta + Math.PI / 2) / 2 * Math.PI * 10;
  205. },
  206. setMedia: function(media) {
  207. this.media = $.extend({}, media);
  208. this.player.jPlayer("setMedia", this.media);
  209. },
  210. play: function(time) {
  211. this.player.jPlayer("play", time);
  212. },
  213. pause: function(time) {
  214. this.player.jPlayer("pause", time);
  215. },
  216. destroy: function() {
  217. this.player.unbind(this.eventNamespace);
  218. this.player.jPlayer("destroy");
  219. }
  220. };