123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682 |
- $(function() {
- /**
- * Ws is a wrapper of the WebSocket API
- * @class Ws
- */
- var Ws = function(options, managerOptions) {
- options = options || {};
- managerOptions = managerOptions || {};
- if (!options.url)
- throw "Cannot open a socket without a url";
- //@TODO: add try catch & retry
- this.onMessageCallbacks = [];
- this.socket = new WebSocket(options.url);
- this.url = options.url;
- this.maxTry = 4;
- this.recoTry = 0;
- };
- /**
- * @method init
- */
- Ws.prototype.init = function(fn) {
- var self = this;
- this.init = true;
- this.socket.onopen = function() {
- self._onOpen(this);
- if (self.init && typeof fn === 'function') {
- //Should be called only once
- fn(this);
- }
- self.init = false;
- };
- this.socket.onmessage = function(e) {
- self.onMessage(e);
- };
- this.socket.onclose = function() {
- self._onClose();
- };
- this.socket.onerror = function(error) {
- self._onError(error);
- };
- };
- /**
- * @method _onOpen
- * @param {Object} e
- * @private
- */
- Ws.prototype._onOpen = function(e) {
- console.log(e);
- this.recoTry = 0;
- };
- /**
- * On message call message callbacks
- * @method onMessage
- * @param {Object} e
- */
- Ws.prototype.onMessage = function(e) {
- //@TODO: is json?
- var message = $.parseJSON(e.data);
- for (var i = 0, length = this.onMessageCallbacks.length; i < length; i++) {
- var cb = this.onMessageCallbacks[i];
- if (typeof cb === 'function') {
- cb(message);
- }
- }
- };
- /**
- * @method _onError
- * @param {Object} e
- * @private
- */
- Ws.prototype._onError = function(e) {
- console.log(e);
- };
- /**
- * @method _onClose
- * @param {Object} e
- * @private
- */
- Ws.prototype._onClose = function(e) {
- console.log(e);
- //try to reco?
- if (this.maxTry > this.recoTry) {
- return;
- }
- this.socket = new WebSocket(this.url);
- this.init();
- this.recoTry++;
- };
- /**
- * Send stringify & send message to the socket
- * @method sendMessage
- * @param message
- */
- Ws.prototype.sendMessage = function(message) {
- message = JSON.stringify(message);
- this.socket.send(message);
- };
- /**
- * @class PlayerControl
- */
- var PlayerControl = function(options) {
- options = options || {};
- if (!options.socket instanceof Ws)
- throw "You need to provide a socket instance";
- if (!options.element || !options.element.length)
- throw "Element not found";
- this._playing = false;
- this._vClicked = false;
- this._progDragged = false;
- this._progClicked = false;
- this.element = options.element;
- this.socket = options.socket;
- this.duration = 0;
- this.currentTime = 0;
- this.volume = 0.5;
- this._previousVolume = 1;
- this.timeInterval = null;
- this.enableInterval = false;
- this.volumeStyleMap = {
- 0: 'mute',
- 1: 'very-low',
- 2: 'very-low',
- 3: 'low',
- 4: 'low',
- 5: 'medium',
- 6: 'medium',
- 7: 'high',
- 8: 'high',
- 9: 'very-high',
- 10: 'very-high'
- };
- };
- /**
- * @method init
- */
- PlayerControl.prototype.init = function() {
- //Add event listener
- var self = this;
- //Update time
- self.updateTime();
- var buttonHeight = self.element.find('.volume-button').height();
- var y = buttonHeight * this.volume * 10;
- self.updateVolume(y);
- var progWidth = this.element.find('.progress').width();
- var x = self.duration ? (progWidth / self.duration) * self.currentTime : 0;
- self.updateProgress(x);
- $(window).bind('mouseup', function(e) {
- self._progClicked = false;
- self._progDragged = false;
- self._vClicked = false;
- self._dragVolume = false;
- });
- // Shortcuts
- $(window).bind('keydown', function(e) {
- switch (e.which) {
- case 32:
- e.preventDefault();
- self.playPause();
- break;
- case 37: // left
- var time = self.currentTime - (self.duration / 100);
- self.seekTo({
- send: true,
- currentTime: time
- });
- break;
- case 39: // right
- var time = self.currentTime + (self.duration / 100);
- self.seekTo({
- send: true,
- currentTime: time
- });
- break;
- case 38: // up
- e.preventDefault();
- var buttonHeight = self.element.find('.volume-button').height();
- var y = buttonHeight * (self.volume + 0.1) * 10;
- self.updateVolume(y);
- break;
- case 40: // down
- e.preventDefault();
- var buttonHeight = self.element.find('.volume-button').height();
- var y = buttonHeight * (self.volume - 0.1) * 10;
- self.updateVolume(y);
- break;
- default:
- return; // exit this handler for other keys
- }
- });
- this.element.find('.play-pause').bind('click', function() {
- self.playPause();
- });
- this.element.find('.progress').bind('mousedown', function(e) {
- self.mousedownProgress(e);
- });
- this.element.bind('mousemove', function(e) {
- if (self._progClicked) {
- self.dragProgress(e);
- }
- if (self._vClicked) {
- self.dragVolume(e);
- }
- });
- this.element.find('.volume-bar-holder').bind('mousedown', function(e) {
- self._vClicked = true;
- var y = self.element.find('.volume-bar-holder').height() - (e.pageY - self.element.find('.volume-bar-holder').offset().top);
- self.updateVolume(y);
- });
- this.element.find('.volume-icon').bind('mousedown', function() {
- if (self.volume) {
- self._previousVolume = self.volume;
- self.volume = 0;
- } else {
- self.volume = self._previousVolume;
- }
- var buttonHeight = self.element.find('.volume-button').height();
- var y = buttonHeight * self.volume * 10;
- self.updateVolume(y);
- });
- };
- /**
- * @method updateVolume
- * @param {number} y
- */
- PlayerControl.prototype.updateVolume = function(y) {
- var buttonHeight = this.element.find('.volume-button').height();
- var barHolderHeight = this.element.find('.volume-bar-holder').height();
- if (y > barHolderHeight) {
- y = barHolderHeight;
- }
- this.element.find('.volume-bar').css({
- height: y + 'px'
- });
- //between 1 and 0
- this.volume = this.element.find('.volume-bar').height() / barHolderHeight;
- this.animateVolume();
- };
- /**
- * @method playPause
- */
- PlayerControl.prototype.playPause = function() {
- if (this.isEnded()) {
- return this.pause();
- }
- if (this._playing) {
- this.pause({
- send: true
- });
- }
- else {
- this.play({
- send: true
- });
- }
- };
- /**
- * @method animateVolume
- */
- PlayerControl.prototype.animateVolume = function() {
- var volumeIcon = this.element.find('.volume-icon');
- volumeIcon.removeClass().addClass('volume-icon v-' + this.volumeStyleMap[parseInt(this.volume * 10)]);
- };
- /**
- * @method getMouseProgressPosition
- * @param {Object} e - DOM event
- * @returns {number}
- */
- PlayerControl.prototype.getMouseProgressPosition = function(e) {
- return e.pageX - this.element.find('.progress').offset().left;
- };
- /**
- * @method mousedownProgress
- * @param {Object} e - DOM event
- */
- PlayerControl.prototype.mousedownProgress = function(e) {
- var progWidth = this.element.find('.progress').width();
- this._progClicked = true;
- var x = this.getMouseProgressPosition(e);
- this.currentTime = Math.round((x / progWidth) * this.duration);
- this.seekTo({
- send: true,
- currentTime: this.currentTime
- });
- };
- /**
- * @method dragProgress
- * @param {Object} e - DOM event
- */
- PlayerControl.prototype.dragProgress = function(e) {
- this._progDragged = true;
- var progMove = 0;
- var x = this.getMouseProgressPosition(e);
- var progWidth = this.element.find('.progress').width();
- if (this._playing && (this.currentTime < this.duration)) {
- this.play();
- }
- if (x <= 0) {
- progMove = 0;
- this.currentTime = 0;
- }
- else if (x > progWidth) {
- this.currentTime = this.duration;
- progMove = progWidth;
- }
- else {
- progMove = x;
- this.currentTime = Math.round((x / progWidth) * this.duration);
- }
- this.seekTo({
- send: true,
- currentTime: this.currentTime
- });
- };
- /**
- * @method dragVolume
- * @param {Object} e - DOM event
- */
- PlayerControl.prototype.dragVolume = function(e) {
- this._dragVolume = true;
- var volHeight = this.element.find('.volume-bar-holder').height();
- var y = volHeight - (e.pageY - this.element.find('.volume-bar-holder').offset().top);
- var volMove = 0;
- if (y <= 0) {
- volMove = 0;
- } else if (y > this.element.find('.volume-bar-holder').height() ||
- (y / volHeight) === 1) {
- volMove = volHeight;
- } else {
- volMove = y;
- }
- this.updateVolume(volMove);
- };
- PlayerControl.prototype.formatTime = function(ms) {
- var timeMatch = new Date(ms).toUTCString().match(/(\d\d:\d\d:\d\d)/);
- return timeMatch.length ? timeMatch[0].replace('00:', '') : '00:00';
- };
- /**
- * @method updateTime
- * @param {number} currentTime
- */
- PlayerControl.prototype.updateTime = function(currentTime) {
- var progWidth, cTime, tTime;
- progWidth = this.element.find('.progress').width();
- currentTime = currentTime ||
- Math.round(($('.progress-bar').width() / progWidth) * this.duration);
- cTime = this.formatTime(currentTime);
- tTime = this.formatTime(this.duration);
- this.element.find('.ctime').text(cTime);
- this.element.find('.ttime').text(tTime);
- };
- /**
- * @method updateProgress
- * @param {number} x
- */
- PlayerControl.prototype.updateProgress = function(x) {
- var buttonWidth = this.element.find('.progress-button').width();
- this.element.find('.progress-bar').css({
- width: x
- });
- this.element.find('.progress-button').css({
- left: x - (2 * buttonWidth) - (buttonWidth / 2) + 'px'
- });
- };
- /**
- * @method isEnded
- * @returns {boolean}
- */
- PlayerControl.prototype.isEnded = function() {
- return this.currentTime >= this.duration;
- };
- /**
- * @method play
- * @param {boolean} send
- */
- PlayerControl.prototype.play = function(options) {
- options = options || {};
- var self = this,
- send = options.send,
- currentTime = options.currentTime,
- delay = 1000,
- progWidth = this.element.find('.progress').width();
- //update currentTime
- this.currentTime = typeof currentTime !== 'undefined' ? currentTime : this.currentTime;
- if (this.timeInterval) {
- clearInterval(this.timeInterval);
- }
- //@TODO: use requestFrame instead
- this._playing = true;
- this.element
- .find('.play-pause')
- .addClass('pause')
- .removeClass('play');
- if (send) {
- this.socket.sendMessage({
- type: 'pause'
- });
- }
- if (!this.enableInterval) {
- return;
- }
- this.timeInterval = setInterval(function() {
- if (self.isEnded()) {
- self.currentTime = self.duration;
- self.pause();
- return clearInterval(this.timeInterval);
- }
- self.currentTime += 1;
- self.updateTime(self.currentTime);
- var x = (progWidth / self.duration) * self.currentTime;
- self.updateProgress(x);
- }, delay);
- };
- /**
- * @method pause
- * @param {boolean} send
- */
- PlayerControl.prototype.pause = function(options) {
- options = options || {};
- var send = options.send,
- currentTime = options.currentTime;
- //update currentTime
- this.currentTime = typeof currentTime !== 'undefined' ? currentTime : this.currentTime;
- if (this.timeInterval) {
- clearInterval(this.timeInterval);
- }
- this._playing = false;
- this.element
- .find('.play-pause')
- .addClass('play')
- .removeClass('pause');
- if (send) {
- this.socket.sendMessage({
- type: 'pause',
- currentTime: this.currentTime,
- media: {
- id: this.id
- }
- });
- }
- };
- /**
- * @method seekTo
- * @param {boolean} send
- * @param {number} time
- */
- PlayerControl.prototype.seekTo = function(options) {
- options = options || {};
- var currentTime = options.currentTime,
- send = options.send;
- //@TODO: make a debounce when dragging & key
- var progWidth = this.element.find('.progress').width();
- if (currentTime < 0) {
- currentTime = 0;
- }
- if (currentTime > this.duration) {
- currentTime = this.duration;
- }
- this.currentTime = currentTime;
- if (this._playing) {
- this.play();
- }
- var x = (progWidth / this.duration) * this.currentTime;
- this.updateProgress(x);
- this.updateTime(this.currentTime);
- if (send) {
- this.socket.sendMessage({
- type: 'seekTo',
- currentTime: currentTime
- });
- }
- };
- /**
- * @method getPlaying
- */
- PlayerControl.prototype.getPlaying = function() {
- //Trigger playing event (from server)
- this.socket.sendMessage({
- type: 'playing'
- });
- };
- /**
- * @method playing
- * @param {Object} message
- */
- PlayerControl.prototype.playing = function(options) {
- if (!options) {
- return;
- }
- options = options || {};
- this.currentTime = options.currentTime;
- this.duration = options.duration;
- this.title = options.title;
- this.id = options.id;
- var titleElement = this.element.find('.title');
- titleElement.text(this.title);
- this.play();
- }
- PlayerControl.prototype.openURL = function(options) {
- options = options || {};
- this.socket.sendMessage({
- type: 'openURL',
- url: options.url
- });
- };
- /**
- * Instanciation of the Ws class
- */
- var URL = 'ws://' + location.host;
- var socket = new Ws({
- url: URL
- });
- var playerControl = new PlayerControl({
- socket: socket,
- element: $('.player-control')
- });
- playerControl.init();
- socket.init(function() {
- playerControl.getPlaying();
- });
- /**
- * Map which method is allowed by event type
- */
- var TYPE_MAP = {
- play: function(message) {
- playerControl.play({
- currentTime: message.currentTime
- });
- },
- pause: function(message) {
- playerControl.pause({
- currentTime: message.currentTime
- });
- },
- playing: function(message) {
- playerControl.playing({
- currentTime: message.currentTime,
- duration: message.media.duration,
- title: message.media.title,
- id: message.media.id
- });
- },
- seekTo: function(message) {
- playerControl.seekTo({
- currentTime: message.currentTime
- });
- }
- };
- /**
- * Manage incoming messages
- */
- socket.onMessageCallbacks.push(function(message) {
- var key = 'type';
- var type = message[key];
- if (!type || typeof TYPE_MAP[type] !== 'function')
- return;
- TYPE_MAP[type](message);
- });
- $('form.open-url').on('submit', function(e) {
- e.preventDefault();
- var url = $(this).find('input').val();
- var localesURL = LOCALES ? (LOCALES.PLAYER_CONTROL ? LOCALES.PLAYER_CONTROL.URL : {}) : {};
- if (!url) {
- return displayMessage(localesURL.EMPTY);
- } else if (!isURL(url)) {
- return displayMessage(localesURL.NOT_VALID);
- }
- displayMessage(localesURL.SENT_SUCCESSFULLY);
- playerControl.openURL({
- url: url
- });
- //clear the form
- $(this).find('input').val('');
- });
- /**
- * Check if a given string is a URL
- * Regex from https://gist.github.com/searls/1033143
- * @param {string} str
- * @returns {boolean}
- */
- function isURL(str) {
- var p = /\b((?:https?:\/\/|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))/i;
- return p.test(str);
- }
- //Display message to the user
- var TIMEOUT = null;
- var DELAY = 5000;
- function displayMessage(message) {
- if (!message) {
- return;
- }
- $('.display-message').addClass('show');
- $('.display-message').text(message);
- if (TIMEOUT) {
- clearTimeout(TIMEOUT);
- }
- TIMEOUT = setTimeout(function() {
- clearMessage();
- }, DELAY);
- }
- function clearMessage() {
- if (TIMEOUT) {
- clearTimeout(TIMEOUT);
- }
- $('.display-message').removeClass('show');
- }
- });
|