playerControl.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570
  1. $(function() {
  2. /**
  3. * Ws is a wrapper of the WebSocket API
  4. * @class Ws
  5. */
  6. var Ws = function(options, managerOptions) {
  7. options = options || {};
  8. managerOptions = managerOptions || {};
  9. if (!options.url)
  10. throw "Cannot open a socket without a url";
  11. //@TODO: add try catch & retry
  12. this.onMessageCallbacks = [];
  13. this.socket = new WebSocket(options.url);
  14. this.url = options.url;
  15. this.maxTry = 4;
  16. this.recoTry = 0;
  17. };
  18. /**
  19. * @method init
  20. */
  21. Ws.prototype.init = function() {
  22. var self = this;
  23. this.socket.onopen = function() {
  24. self._onOpen(this);
  25. };
  26. this.socket.onmessage = function(e) {
  27. self.onMessage(e);
  28. };
  29. this.socket.onclose = function() {
  30. self._onClose();
  31. };
  32. this.socket.onerror = function(error) {
  33. self._onError(error);
  34. };
  35. };
  36. /**
  37. * @method _onOpen
  38. * @param {Object} e
  39. * @private
  40. */
  41. Ws.prototype._onOpen = function(e) {
  42. console.log(e);
  43. this.recoTry = 0;
  44. };
  45. /**
  46. * On message call message callbacks
  47. * @method onMessage
  48. * @param {Object} e
  49. */
  50. Ws.prototype.onMessage = function(e) {
  51. //@TODO: is json?
  52. var message = $.parseJSON(e.data);
  53. for (var i = 0, length = this.onMessageCallbacks.length; i < length; i++) {
  54. var cb = this.onMessageCallbacks[i];
  55. if (typeof cb === 'function') {
  56. cb(message);
  57. }
  58. }
  59. };
  60. /**
  61. * @method _onError
  62. * @param {Object} e
  63. * @private
  64. */
  65. Ws.prototype._onError = function(e) {
  66. console.log(e);
  67. };
  68. /**
  69. * @method _onClose
  70. * @param {Object} e
  71. * @private
  72. */
  73. Ws.prototype._onClose = function(e) {
  74. console.log(e);
  75. //try to reco?
  76. if (this.maxTry > this.recoTry) {
  77. return;
  78. }
  79. this.socket = new WebSocket(this.url);
  80. this.init();
  81. this.recoTry++;
  82. };
  83. /**
  84. * Send stringify & send message to the socket
  85. * @method sendMessage
  86. * @param message
  87. */
  88. Ws.prototype.sendMessage = function(message) {
  89. message = JSON.stringify(message);
  90. this.socket.send(message);
  91. };
  92. /**
  93. * @class PlayerControl
  94. */
  95. var PlayerControl = function(options) {
  96. options = options || {};
  97. if (!options.socket instanceof Ws)
  98. throw "You need to provide a socket instance";
  99. if (!options.element || !options.element.length)
  100. throw "Element not found";
  101. this._playing = false;
  102. this._vClicked = false;
  103. this._progDragged = false;
  104. this._progClicked = false;
  105. this.element = options.element;
  106. this.socket = options.socket;
  107. this.duration = 7200;
  108. this.currentTime = 0;
  109. this.volume = 0.5;
  110. this._previousVolume = 1;
  111. this.timeInterval = null;
  112. this.enableInterval = false;
  113. };
  114. /**
  115. * @method init
  116. */
  117. PlayerControl.prototype.init = function() {
  118. //Add event listener
  119. var self = this;
  120. //Update time
  121. self.updateTime();
  122. var buttonHeight = self.element.find('.volume-button').height();
  123. var y = buttonHeight * this.volume * 10;
  124. self.updateVolume(y);
  125. var progWidth = this.element.find('.progress').width();
  126. var x = (progWidth / self.duration) * self.currentTime;
  127. self.updateProgress(x);
  128. $(window).bind('mouseup', function(e) {
  129. self._progClicked = false;
  130. self._progDragged = false;
  131. self._vClicked = false;
  132. self._dragVolume = false;
  133. });
  134. // Shortcuts
  135. $(window).bind('keydown', function(e) {
  136. switch (e.which) {
  137. case 32:
  138. e.preventDefault();
  139. self.playPause();
  140. break;
  141. case 37: // left
  142. var time = self.currentTime - (self.duration / 100);
  143. self.seekTo(true, time);
  144. break;
  145. case 39: // right
  146. var time = self.currentTime + (self.duration / 100);
  147. self.seekTo(true, time);
  148. break;
  149. case 38: // up
  150. e.preventDefault();
  151. var buttonHeight = self.element.find('.volume-button').height();
  152. var y = buttonHeight * (self.volume + 0.1) * 10;
  153. self.updateVolume(y);
  154. break;
  155. case 40: // down
  156. e.preventDefault();
  157. var buttonHeight = self.element.find('.volume-button').height();
  158. var y = buttonHeight * (self.volume - 0.1) * 10;
  159. self.updateVolume(y);
  160. break;
  161. default:
  162. return; // exit this handler for other keys
  163. }
  164. });
  165. this.element.find('.play-pause').bind('click', function() {
  166. self.playPause();
  167. });
  168. this.element.find('.progress').bind('mousedown', function(e) {
  169. self.mousedownProgress(e);
  170. });
  171. this.element.bind('mousemove', function(e) {
  172. if (self._progClicked) {
  173. self.dragProgress(e);
  174. }
  175. if (self._vClicked) {
  176. self.dragVolume(e);
  177. }
  178. });
  179. this.element.find('.volume-bar-holder').bind('mousedown', function(e) {
  180. self._vClicked = true;
  181. var y = self.element.find('.volume-bar-holder').height() - (e.pageY - self.element.find('.volume-bar-holder').offset().top);
  182. self.updateVolume(y);
  183. });
  184. this.element.find('.volume-icon').bind('mousedown', function() {
  185. if (self.volume) {
  186. self._previousVolume = self.volume;
  187. self.volume = 0;
  188. } else {
  189. self.volume = self._previousVolume;
  190. }
  191. var buttonHeight = self.element.find('.volume-button').height();
  192. var y = buttonHeight * self.volume * 10;
  193. self.updateVolume(y);
  194. });
  195. };
  196. /**
  197. * @method updateVolume
  198. * @param {number} y
  199. */
  200. PlayerControl.prototype.updateVolume = function(y) {
  201. var buttonHeight = this.element.find('.volume-button').height();
  202. var barHolderHeight = this.element.find('.volume-bar-holder').height();
  203. if (y > barHolderHeight) {
  204. y = barHolderHeight;
  205. }
  206. this.element.find('.volume-bar').css({
  207. height: y + 'px'
  208. });
  209. //between 1 and 0
  210. this.volume = this.element.find('.volume-bar').height() / barHolderHeight;
  211. this.animateVolume();
  212. };
  213. /**
  214. * @method playPause
  215. */
  216. PlayerControl.prototype.playPause = function() {
  217. if (this.isEnded()) {
  218. return this.pause();
  219. }
  220. if (this._playing) {
  221. this.pause(true);
  222. }
  223. else {
  224. this.play(true);
  225. }
  226. };
  227. /**
  228. * @method animateVolume
  229. */
  230. PlayerControl.prototype.animateVolume = function() {
  231. var volumeIcon = this.element.find('.volume-icon');
  232. volumeIcon.removeClass().addClass('volume-icon v-change-' + parseInt(this.volume * 10));
  233. };
  234. /**
  235. * @method getMouseProgressPosition
  236. * @param {Object} e - DOM event
  237. * @returns {number}
  238. */
  239. PlayerControl.prototype.getMouseProgressPosition = function(e) {
  240. return e.pageX - this.element.find('.progress').offset().left;
  241. };
  242. /**
  243. * @method mousedownProgress
  244. * @param {Object} e - DOM event
  245. */
  246. PlayerControl.prototype.mousedownProgress = function(e) {
  247. var progWidth = this.element.find('.progress').width();
  248. this._progClicked = true;
  249. var x = this.getMouseProgressPosition(e);
  250. this.currentTime = Math.round((x / progWidth) * this.duration);
  251. this.seekTo(true, this.currentTime);
  252. };
  253. /**
  254. * @method dragProgress
  255. * @param {Object} e - DOM event
  256. */
  257. PlayerControl.prototype.dragProgress = function(e) {
  258. this._progDragged = true;
  259. var progMove = 0;
  260. var x = this.getMouseProgressPosition(e);
  261. var progWidth = this.element.find('.progress').width();
  262. if (this._playing && (this.currentTime < this.duration)) {
  263. this.play();
  264. }
  265. if (x <= 0) {
  266. progMove = 0;
  267. this.currentTime = 0;
  268. }
  269. else if (x > progWidth) {
  270. this.currentTime = this.duration;
  271. progMove = progWidth;
  272. }
  273. else {
  274. progMove = x;
  275. this.currentTime = Math.round((x / progWidth) * this.duration);
  276. }
  277. this.seekTo(true, this.currentTime);
  278. };
  279. /**
  280. * @method dragVolume
  281. * @param {Object} e - DOM event
  282. */
  283. PlayerControl.prototype.dragVolume = function(e) {
  284. this._dragVolume = true;
  285. var volHeight = this.element.find('.volume-bar-holder').height();
  286. var y = volHeight - (e.pageY - this.element.find('.volume-bar-holder').offset().top);
  287. var volMove = 0;
  288. if (y <= 0) {
  289. volMove = 0;
  290. } else if (y > this.element.find('.volume-bar-holder').height() ||
  291. (y / volHeight) === 1) {
  292. volMove = volHeight;
  293. } else {
  294. volMove = y;
  295. }
  296. this.updateVolume(volMove);
  297. };
  298. /**
  299. * @method updateTime
  300. * @param {number} currentTime
  301. */
  302. PlayerControl.prototype.updateTime = function(currentTime) {
  303. var progWidth = this.element.find('.progress').width();
  304. currentTime = currentTime ||
  305. Math.round(($('.progress-bar').width() / progWidth) * this.duration);
  306. currentTime /= 1000;
  307. var duration = this.duration / 1000;
  308. var seconds = 0,
  309. minutes = Math.floor(currentTime / 60),
  310. tminutes = Math.round(duration / 60),
  311. tseconds = Math.round((duration) - (tminutes * 60));
  312. seconds = Math.round(currentTime) - (60 * minutes);
  313. if (seconds > 59) {
  314. seconds = Math.round(currentTime) - (60 * minutes);
  315. if (seconds === 60) {
  316. minutes = Math.round(currentTime / 60);
  317. seconds = 0;
  318. }
  319. }
  320. // Set a zero before the number if less than 10.
  321. if (seconds < 10) {
  322. seconds = '0' + seconds;
  323. }
  324. if (tseconds < 10) {
  325. tseconds = '0' + tseconds;
  326. }
  327. this.element.find('.ctime').html(minutes + ':' + seconds);
  328. this.element.find('.ttime').html(tminutes + ':' + tseconds);
  329. };
  330. /**
  331. * @method updateProgress
  332. * @param {number} x
  333. */
  334. PlayerControl.prototype.updateProgress = function(x) {
  335. var buttonWidth = this.element.find('.progress-button').width();
  336. this.element.find('.progress-bar').css({
  337. width: x
  338. });
  339. this.element.find('.progress-button').css({
  340. left: x - (2 * buttonWidth) - (buttonWidth / 2) + 'px'
  341. });
  342. };
  343. /**
  344. * @method isEnded
  345. * @returns {boolean}
  346. */
  347. PlayerControl.prototype.isEnded = function() {
  348. return this.currentTime >= this.duration;
  349. };
  350. /**
  351. * @method play
  352. * @param {boolean} send
  353. */
  354. PlayerControl.prototype.play = function(send) {
  355. var self = this;
  356. var progWidth = this.element.find('.progress').width();
  357. var delay = 1000;
  358. if (this.timeInterval) {
  359. clearInterval(this.timeInterval);
  360. }
  361. //@TODO: use requestFrame instead
  362. this._playing = true;
  363. this.element
  364. .find('.play-pause')
  365. .addClass('pause')
  366. .removeClass('play');
  367. if (send) {
  368. this.socket.sendMessage({
  369. type: 'pause'
  370. });
  371. }
  372. if (!this.enableInterval) {
  373. return;
  374. }
  375. this.timeInterval = setInterval(function() {
  376. if (self.isEnded()) {
  377. self.currentTime = self.duration;
  378. self.pause();
  379. return clearInterval(this.timeInterval);
  380. }
  381. self.currentTime += 1;
  382. self.updateTime(self.currentTime);
  383. var x = (progWidth / self.duration) * self.currentTime;
  384. self.updateProgress(x);
  385. }, delay);
  386. };
  387. /**
  388. * @method pause
  389. * @param {boolean} send
  390. */
  391. PlayerControl.prototype.pause = function(send) {
  392. if (this.timeInterval) {
  393. clearInterval(this.timeInterval);
  394. }
  395. this._playing = false;
  396. this.element
  397. .find('.play-pause')
  398. .addClass('play')
  399. .removeClass('pause');
  400. if (send) {
  401. this.socket.sendMessage({
  402. type: 'pause',
  403. currentTime: this.currentTime,
  404. media: {
  405. id: this.id
  406. }
  407. });
  408. }
  409. };
  410. /**
  411. * @method seekTo
  412. * @param {boolean} send
  413. * @param {number} time
  414. */
  415. PlayerControl.prototype.seekTo = function(send, time) {
  416. //@TODO: make a debounce when dragging & key
  417. var progWidth = this.element.find('.progress').width();
  418. if (time < 0) {
  419. time = 0;
  420. }
  421. if (time > this.duration) {
  422. time = this.duration;
  423. }
  424. this.currentTime = time;
  425. if (this._playing) {
  426. this.play();
  427. }
  428. var x = (progWidth / this.duration) * this.currentTime;
  429. this.updateProgress(x);
  430. this.updateTime(this.currentTime);
  431. if (send) {
  432. this.socket.sendMessage({
  433. type: 'seekTo',
  434. currentTime: time
  435. });
  436. }
  437. };
  438. /**
  439. * @method goTo
  440. * @param {boolean} send
  441. */
  442. PlayerControl.prototype.goTo = function(send) {
  443. this.socket.sendMessage({
  444. type: 'goTo'
  445. });
  446. };
  447. /**
  448. * @method playing
  449. * @param {Object} message
  450. */
  451. PlayerControl.prototype.playing = function(message) {
  452. this.currentTime = message.currentTime;
  453. this.duration = message.media.duration;
  454. this.title = message.media.title;
  455. this.id = message.media.id;
  456. var titleElement = this.element.find('.title');
  457. titleElement.text(this.title);
  458. this.play();
  459. }
  460. /**
  461. * Instanciation of the Ws class
  462. */
  463. //@TODO: This URL need to be updated at runtime
  464. var URL = 'ws://192.168.0.14:8888';
  465. var socket = new Ws({
  466. url: URL
  467. });
  468. socket.init();
  469. var playerControl = new PlayerControl({
  470. socket: socket,
  471. element: $('.player-control')
  472. });
  473. playerControl.init();
  474. /**
  475. * Map which method is allowed by event type
  476. */
  477. var TYPE_MAP = {
  478. play: function(message) {
  479. playerControl.play(message);
  480. },
  481. pause: function(message) {
  482. playerControl.pause(message);
  483. },
  484. playing: function(message) {
  485. playerControl.playing(message);
  486. },
  487. seekTo: function(message) {
  488. playerControl.seekTo(null, message.currentTime);
  489. }
  490. };
  491. /**
  492. * Manage incoming messages
  493. */
  494. socket.onMessageCallbacks.push(function(message) {
  495. var key = 'type';
  496. var type = message[key];
  497. if (!type || typeof TYPE_MAP[type] !== 'function')
  498. return;
  499. TYPE_MAP[type](message);
  500. });
  501. });