From d672828022e5886436237f94eaecefd5e36df123 Mon Sep 17 00:00:00 2001 From: Pacien TRAN-GIRARD Date: Mon, 12 May 2014 21:43:55 +0200 Subject: Implement remote control --- js/slide-controller.js | 187 ++++++++++++++++++++++++++++++++++--------------- js/slide-deck.js | 32 ++++++--- js/slides.js | 5 +- slide_config.js | 6 +- 4 files changed, 158 insertions(+), 72 deletions(-) diff --git a/js/slide-controller.js b/js/slide-controller.js index 9dcb4df..a247b91 100644 --- a/js/slide-controller.js +++ b/js/slide-controller.js @@ -1,82 +1,140 @@ +/** + * Remote control by: + * + * @authors Pacien TRAN-GIRARD + */ (function(window) { var ORIGIN_ = location.protocol + '//' + location.host; - function SlideController() { - this.popup = null; - this.isPopup = window.opener; + function SlideController(deck) { + this.deck = deck; - if (this.setupDone()) { - window.addEventListener('message', this.onMessage_.bind(this), - false); + this.mode = null; + this.remoteSocket = null; + this.isPresenter = window.opener; - // Close popups if we reload the main window. - window.addEventListener('beforeunload', function(e) { - if (this.popup) { - this.popup.close(); - } - }.bind(this), false); - } + this.keyLock = null; + + this.setup(); } - SlideController.PRESENTER_MODE_PARAM = 'presentme'; + SlideController.MODES = [ 'local', 'remote', 'controller' ]; + + SlideController.prototype.setup = function() { - SlideController.prototype.setupDone = function() { + var self = this; + + // find the current mode var params = location.search.substring(1).split('&').map(function(el) { return el.split('='); }); - var presentMe = null; - for (var i = 0, param; param = params[i]; ++i) { - if (param[0].toLowerCase() == SlideController.PRESENTER_MODE_PARAM) { - presentMe = param[1] == 'true'; - break; - } - } + var paramKeys = params[0]; - if (presentMe !== null) { - localStorage.ENABLE_PRESENTOR_MODE = presentMe; - // TODO: use window.history.pushState to update URL instead of the - // redirect. - if (window.history.replaceState) { - window.history.replaceState({}, '', location.pathname); - } else { - location.replace(location.pathname); - return false; + SlideController.MODES.forEach(function(element, index, array) { + if (paramKeys.indexOf(element) > -1) { + self.mode = element; + return; } - } + }); + + console.log("Control mode: " + this.mode); + + // clean the location bar + // if (this.mode !== null) { + // // localStorage.ENABLE_PRESENTOR_MODE = presentMe; + // if (window.history.pushState) { + // window.history.pushState({}, '', location.pathname); + // } else if (window.history.replaceState) { + // window.history.replaceState({}, '', location.pathname); + // } + // // else { + // // location.replace(location.pathname); + // // return false; + // // } + // } + + // activate the mode specific behaviour + switch (this.mode) { + + case 'local': + // Only open popup from main deck. Avoid recursive popupception. + if (!this.isPresenter) { + var opts = 'menubar=no,location=yes,resizable=yes,scrollbars=no,status=no'; + var localPresenter = window.open(location.href, 'mywindow', + opts); + + // Loading in the popup? Turn the presenter mode on. + localPresenter.addEventListener('load', function(e) { + localPresenter.document.body.classList + .add('with-notes'); + }.bind(this), false); + + window.addEventListener('message', this.onMessage_ + .bind(this), false); + + // Close popups if we reload the main window. + window.addEventListener('beforeunload', function(e) { + localPresenter.close(); + }.bind(this), false); + } + + break; + + case 'controller': + this.isPresenter = true; + document.body.classList.add('popup'); + document.body.classList.add('with-notes'); + var password = prompt("Broadcaster password"); + + case 'remote': + var addr = this.deck.config_.settings.remoteSocket; + var channel = this.deck.config_.settings.remoteChannel; + var password = (password != null) ? password : ''; + this.remoteSocket = io.connect(addr, { + 'query' : 'channel=' + channel + '&password=' + password, + 'force new connection' : true, + }); + + this.remoteSocket.on('connect', function() { + var message = 'Connected to ' + channel + '@' + addr; + console.log(message); + alert(message); + }); + + this.remoteSocket.on('disconnect', function() { + var message = 'Diconnected from' + channel + '@' + addr; + console.log(message); + alert(message); + }); + + this.remoteSocket.on('message', function(message) { + console.log('Received from remote: ' + message); + self.onMessage_({ + data : { + keyCode : parseInt(message[0]) + } + }); + }); + + break; - var enablePresenterMode = localStorage.getItem('ENABLE_PRESENTOR_MODE'); - if (enablePresenterMode && JSON.parse(enablePresenterMode)) { - // Only open popup from main deck. Don't want recursive popup - // opening! - if (!this.isPopup) { - var opts = 'menubar=no,location=yes,resizable=yes,scrollbars=no,status=no'; - this.popup = window.open(location.href, 'mywindow', opts); - - // Loading in the popup? Trigger the hotkey for turning - // presenter mode on. - this.popup.addEventListener('load', function(e) { - var evt = this.popup.document.createEvent('Event'); - evt.initEvent('keydown', true, true); - evt.keyCode = 'P'.charCodeAt(0); - this.popup.document.dispatchEvent(evt); - // this.popup.document.body.classList.add('with-notes'); - // document.body.classList.add('popup'); - }.bind(this), false); - } } return true; } SlideController.prototype.onMessage_ = function(e) { + console.log("Received event: " + JSON.stringify(e)); + var data = e.data; // Restrict messages to being from this origin. Allow local developmet // from file:// though. // TODO: It would be dope if FF implemented location.origin! - if (e.origin != ORIGIN_ && ORIGIN_.indexOf('file://') != 0) { + if (this.mode === 'local' && e.origin != ORIGIN_ + && ORIGIN_.indexOf('file://') != 0) { alert('Someone tried to postMessage from an unknown origin'); return; } @@ -87,6 +145,8 @@ // } if ('keyCode' in data) { + this.keyLock = data.keyCode; + var evt = document.createEvent('Event'); evt.initEvent('keydown', true, true); evt.keyCode = data.keyCode; @@ -95,15 +155,28 @@ }; SlideController.prototype.sendMsg = function(msg) { + + if (msg.keyCode === this.keyLock) { + this.keyLock = null; + return; + } + + console.log("Sending: " + JSON.stringify(msg)); + // // Send message to popup window. - // if (this.popup) { - // this.popup.postMessage(msg, ORIGIN_); + // if (this.localPresenter) { + // this.localPresenter.postMessage(msg, ORIGIN_); // } // Send message to main window. - if (this.isPopup) { - // TODO: It would be dope if FF implemented location.origin. - window.opener.postMessage(msg, '*'); + if (this.isPresenter) { + if (this.mode === 'local') { + // TODO: It would be dope if FF implemented location.origin. + window.opener.postMessage(msg, '*'); + } + if (this.mode === 'controller') { + this.remoteSocket.emit('message', msg.keyCode); + } } }; diff --git a/js/slide-deck.js b/js/slide-deck.js index 67aa4f2..61fe1c0 100644 --- a/js/slide-deck.js +++ b/js/slide-deck.js @@ -65,7 +65,7 @@ SlideDeck.prototype.loadSlide = function(slideNo) { */ SlideDeck.prototype.onDomLoaded_ = function(e) { document.body.classList.add('loaded'); // Add loaded class for templates to - // use. + // use. this.slides = this.container .querySelectorAll('slide:not([hidden]):not(.hidden):not(.backdrop)'); @@ -111,7 +111,7 @@ SlideDeck.prototype.onDomLoaded_ = function(e) { // // Also, no need to set this up if we're on mobile. // if (!Modernizr.touch) { this.controller = new SlideController(this); - if (this.controller.isPopup) { + if (this.controller.isPresenter) { document.body.classList.add('popup'); } // } @@ -177,7 +177,7 @@ SlideDeck.prototype.onBodyKeyDown_ = function(e) { } // Forward keydowns to the main slides if we're the popup. - if (this.controller && this.controller.isPopup) { + if (this.controller && this.controller.isPresenter) { this.controller.sendMsg({ keyCode : e.keyCode }); @@ -223,7 +223,7 @@ SlideDeck.prototype.onBodyKeyDown_ = function(e) { break; case 80: // P - if (this.controller && this.controller.isPopup) { + if (this.controller && this.controller.isPresenter) { document.body.classList.toggle('with-notes'); } else if (this.controller && !this.controller.popup) { document.body.classList.toggle('with-notes'); @@ -275,7 +275,7 @@ SlideDeck.prototype.onBodyKeyDown_ = function(e) { }; /** - * + * */ SlideDeck.prototype.focusOverview_ = function() { var overview = document.body.classList.contains('overview'); @@ -425,11 +425,21 @@ SlideDeck.prototype.loadConfig_ = function(config) { var hammer = new Hammer(this.container); hammer.ondragend = function(e) { - if (e.direction == 'right' || e.direction == 'down') { - self.prevSlide(); - } else if (e.direction == 'left' || e.direction == 'up') { - self.nextSlide(); + var evt = document.createEvent('Event'); + evt.initEvent('keydown', true, true); + + switch (e.direction) { + case 'right': + // previous slide + evt.keyCode = 37; + break; + case 'left': + // next slide + evt.keyCode = 39; + break; } + + document.dispatchEvent(evt); }; } }; @@ -488,7 +498,7 @@ SlideDeck.prototype.prevSlide = function(opt_dontPush) { // Toggle off speaker notes if they're showing when we move backwards on // the // main slides. If we're the speaker notes popup, leave them up. - if (this.controller && !this.controller.isPopup) { + if (this.controller && !this.controller.isPresenter) { bodyClassList.remove('with-notes'); } else if (!this.controller) { bodyClassList.remove('with-notes'); @@ -516,7 +526,7 @@ SlideDeck.prototype.nextSlide = function(opt_dontPush) { // Toggle off speaker notes if they're showing when we advanced on the // main // slides. If we're the speaker notes popup, leave them up. - if (this.controller && !this.controller.isPopup) { + if (this.controller && !this.controller.isPresenter) { bodyClassList.remove('with-notes'); } else if (!this.controller) { bodyClassList.remove('with-notes'); diff --git a/js/slides.js b/js/slides.js index 156fb03..9966d0b 100644 --- a/js/slides.js +++ b/js/slides.js @@ -1,5 +1,6 @@ require([ 'order!../slide_config', 'order!modernizr.custom.45394', - 'order!prettify/prettify', 'order!hammer', 'order!slide-controller', - 'order!slide-deck' ], function(someModule) { + 'order!prettify/prettify', 'order!hammer', + 'order!//webcastor.herokuapp.com/socket.io/socket.io.js', + 'order!slide-controller', 'order!slide-deck' ], function(someModule) { }); diff --git a/slide_config.js b/slide_config.js index 8cb5c2e..83ab855 100644 --- a/slide_config.js +++ b/slide_config.js @@ -20,8 +20,10 @@ var SLIDE_CONFIG = { favIcon : 'images/google_developers_logo_tiny.png', fonts : [ 'Open Sans:regular,semibold,italic,italicsemibold', 'Source Code Pro' ], - // theme: ['mytheme'], // Add your own custom themes or styles in - // /theme/css. Leave off the .css extension. + // theme: ['mytheme'], // Add your own custom themes or styles in + // /theme/css. Leave off the .css extension. + remoteSocket : "https://webcastor.herokuapp.com/", + remoteChannel : "HV2F4V4R", }, // Author information -- cgit v1.2.3