Browse Source

merge in multiplex (#98)

Hakim El Hattab 11 years ago
parent
commit
2bd5e8b65b
9 changed files with 240 additions and 9 deletions
  1. 11 0
      README.md
  2. 12 1
      index.html
  3. 7 2
      js/reveal.js
  4. 1 1
      js/reveal.min.js
  5. 7 5
      package.json
  6. 13 0
      plugin/multiplex/client.js
  7. 48 0
      plugin/multiplex/index.js
  8. 32 0
      plugin/multiplex/master.js
  9. 109 0
      plugin/multiplex/notes.html

+ 11 - 0
README.md

@@ -344,6 +344,17 @@ Reveal.toggleOverview();
 ### Fullscreen mode
 Just press »F« on your keyboard to show your presentation in fullscreen mode. Press the »ESC« key to exit fullscreen mode.
 
+## Multiplexing
+
+The multiplex plugin allows your audience to view the slides on their own phone, tablet or laptop. As the master navigates the slides, all clients will update in real time. See a demo at [http://revealjs.jit.su/](http://revealjs.jit.su)
+
+Configuration is via the multiplex object in index.html. To generate unique secret and token values, visit [revealjs.jit.su/token](revealjs.jit.su/token)
+
+multiplex.secret should only be configured on those pages you wish to be able to control slide navigatoin for all clients. Multi-master configurations work, but if you don't wish your audience to be able to control your slides, set the secret to null. In this master/slave setup, you should create a publicly accessible page with secret set to null, and a protected page containing your secret.
+
+You are very welcome to use the server running at reveal.jit.su, however availability and stability are not guaranteed. For anything mission critical I recommend you run your own server. It is simple to deploy to nodejitsu or run on your own environment.
+
+### Known Issues
 
 ## PDF Export
 

+ 12 - 1
index.html

@@ -358,6 +358,14 @@ function linkify( selector ) {
 
 				theme: Reveal.getQueryHash().theme, // available themes are in /css/theme
 				transition: Reveal.getQueryHash().transition || 'default', // default/cube/page/concave/zoom/linear/fade/none
+				globals: {
+					// Generate a unique id and secret at http://revealjs.jit.su/token
+					multiplex: {
+						id: '7d10234555b923e2',
+						secret: '13627859051503309760',
+						url: 'revealjs.jit.su:80'
+					}
+				},
 
 				// Optional libraries used to extend on reveal.js
 				dependencies: [
@@ -366,8 +374,11 @@ function linkify( selector ) {
 					{ src: 'plugin/markdown/markdown.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
 					{ src: 'plugin/highlight/highlight.js', async: true, callback: function() { hljs.initHighlightingOnLoad(); } },
 					{ src: 'plugin/zoom-js/zoom.js', async: true, condition: function() { return !!document.body.classList; } },
+					// { src: 'plugin/remotes/remotes.js', async: true, condition: function() { return !!document.body.classList; } },
+					// { src: 'socket.io/socket.io.js', async: true, condition: function() { return !!document.body.classList; } },
+					// { src: 'plugin/multiplex/client.js', async: true, condition: function() { return !!document.body.classList; } },
+					// { src: 'plugin/multiplex/master.js', async: true, condition: function() { return !!document.body.classList; } },
 					{ src: 'plugin/notes/notes.js', async: true, condition: function() { return !!document.body.classList; } }
-					// { src: 'plugin/remotes/remotes.js', async: true, condition: function() { return !!document.body.classList; } }
 				]
 			});
 

+ 7 - 2
js/reveal.js

@@ -160,6 +160,9 @@ var Reveal = (function(){
 		// Copy options over to our config object
 		extend( config, options );
 
+		// Push up globals
+		window.globals = config.globals;
+
 		// Hide the address bar in mobile browsers
 		hideAddressBar();
 
@@ -1019,8 +1022,9 @@ var Reveal = (function(){
 	 * @param {int} v Vertical index of the target slide
 	 * @param {int} f Optional index of a fragment within the
 	 * target slide to activate
+	 * @param {int} o Optional origin for use in multimaster environments
 	 */
-	function slide( h, v, f ) {
+	function slide( h, v, f, o ) {
 
 		// Remember where we were at before
 		previousSlide = currentSlide;
@@ -1115,7 +1119,8 @@ var Reveal = (function(){
 				'indexh': indexh,
 				'indexv': indexv,
 				'previousSlide': previousSlide,
-				'currentSlide': currentSlide
+				'currentSlide': currentSlide,
+				'origin': o
 			} );
 		}
 		else {

File diff suppressed because it is too large
+ 1 - 1
js/reveal.min.js


+ 7 - 5
package.json

@@ -3,6 +3,11 @@
   "version": "2.3.0",
   "description": "The HTML Presentation Framework",
   "homepage": "http://lab.hakim.se/reveal-js",
+  "subdomain": "revealjs",
+  "scripts": {
+    "test": "grunt jshint",
+    "start": ""
+  },
   "author": {
     "name": "Hakim El Hattab",
     "email": "hakim.elhattab@gmail.com",
@@ -15,14 +20,11 @@
   "engines": {
     "node": "~0.8.0"
   },
-  "scripts": {
-    "test": "grunt jshint"
-  },
   "dependencies": {
     "underscore": "~1.3.3",
     "express": "~2.5.9",
-    "socket.io": "~0.9.6",
-    "mustache": "~0.4.0"
+    "mustache": "~0.4.0",
+    "socket.io": "~0.9.13"
   },
   "devDependencies": {
     "grunt-contrib-jshint": "~0.2.0",

+ 13 - 0
plugin/multiplex/client.js

@@ -0,0 +1,13 @@
+(function() {
+	var multiplex = window.globals.multiplex;
+	var socketId = multiplex.id;
+	var socket = io.connect(multiplex.url);
+
+	socket.on(multiplex.id, function(data) {
+		// ignore data from sockets that aren't ours
+		if (data.socketId !== socketId) { return; }
+		if( window.location.host === 'localhost:1947' ) return;
+
+		Reveal.slide(data.indexh, data.indexv, null, 'remote');
+	});
+}());

+ 48 - 0
plugin/multiplex/index.js

@@ -0,0 +1,48 @@
+var express		= require('express');
+var fs			= require('fs');
+var io			= require('socket.io');
+var crypto		= require('crypto');
+
+var app			= express.createServer();
+var staticDir	= express.static;
+
+io				= io.listen(app);
+
+var opts = {
+	port: 1948,
+	baseDir : __dirname + '/../../'
+};
+
+io.sockets.on('connection', function(socket) {
+	socket.on('slidechanged', function(slideData) {
+		if (createHash(slideData.secret) === slideData.socketId) {
+			slideData.secret = null;
+			socket.broadcast.emit(slideData.socketId, slideData);
+		};
+	});
+});
+
+app.configure(function() {
+	[ 'css', 'js', 'plugin', 'lib' ].forEach(function(dir) {
+		app.use('/' + dir, staticDir(opts.baseDir + dir));
+	});
+});
+
+app.get("/", function(req, res) {
+	fs.createReadStream(opts.baseDir + '/index.html').pipe(res);
+});
+
+app.get("/token", function(req,res) {
+	var ts = new Date().getTime();
+	var rand = Math.floor(Math.random()*9999999);
+	var secret = ts.toString() + rand.toString();
+	res.send({secret: secret, socketId: createHash(secret)});
+});
+
+var createHash = function(secret) {
+	var cipher = crypto.createCipher('blowfish', secret);
+	return(cipher.final('hex'));
+};
+
+// Actually listen
+app.listen(opts.port || null);

+ 32 - 0
plugin/multiplex/master.js

@@ -0,0 +1,32 @@
+(function() {
+	// don't emit events from inside the previews themselves
+	if ( window.location.search.match( /receiver/gi ) ) { return; }
+	var multiplex = window.globals.multiplex;
+
+	var socket = io.connect(multiplex.url);
+
+	Reveal.addEventListener( 'slidechanged', function( event ) {
+		var nextindexh;
+		var nextindexv;
+		var slideElement = event.currentSlide;
+
+		if (slideElement.nextElementSibling && slideElement.parentNode.nodeName == 'SECTION') {
+			nextindexh = event.indexh;
+			nextindexv = event.indexv + 1;
+		} else {
+			nextindexh = event.indexh + 1;
+			nextindexv = 0;
+		}
+
+		var slideData = {
+			indexh : event.indexh,
+			indexv : event.indexv,
+			nextindexh : nextindexh,
+			nextindexv : nextindexv,
+			secret: multiplex.secret,
+			socketId : multiplex.id
+		};
+
+		if( typeof event.origin === 'undefined' && event.origin !== 'remote' ) socket.emit('slidechanged', slideData);
+	} );
+}());

+ 109 - 0
plugin/multiplex/notes.html

@@ -0,0 +1,109 @@
+<!doctype html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8">
+
+		<title>reveal.js - Slide Notes</title>
+
+		<style>
+			body {
+				font-family: Helvetica;
+			}
+
+			#notes {
+				font-size: 24px;
+				width: 640px;
+				margin-top: 5px;
+			}
+
+			#wrap-current-slide {
+				width: 640px;
+				height: 512px;
+				float: left;
+				overflow: hidden;
+			}
+
+			#current-slide {
+				width: 1280px;
+				height: 1024px;
+				border: none;
+				-moz-transform: scale(0.5);
+				-moz-transform-origin: 0 0;
+				-o-transform: scale(0.5);
+				-o-transform-origin: 0 0;
+				-webkit-transform: scale(0.5);
+				-webkit-transform-origin: 0 0;
+			}
+
+			#wrap-next-slide {
+				width: 320px;
+				height: 256px;
+				float: left;
+				margin: 0 0 0 10px;
+				overflow: hidden;
+			}
+
+			#next-slide {
+				width: 1280px;
+				height: 1024px;
+				border: none;
+				-moz-transform: scale(0.25);
+				-moz-transform-origin: 0 0;
+				-o-transform: scale(0.25);
+				-o-transform-origin: 0 0;
+				-webkit-transform: scale(0.25);
+				-webkit-transform-origin: 0 0;
+			}
+
+			.slides {
+				position: relative;
+				margin-bottom: 10px;
+				border: 1px solid black;
+				border-radius: 2px;
+				background: rgb(28, 30, 32);
+			}
+
+			.slides span {
+				position: absolute;
+				top: 3px;
+				left: 3px;
+				font-weight: bold;
+				font-size: 14px;
+				color: rgba( 255, 255, 255, 0.9 );
+			}
+		</style>
+	</head>
+
+	<body>
+
+		<div id="wrap-current-slide" class="slides">
+			<iframe src="/?receiver" width="1280" height="1024" id="current-slide"></iframe>
+		</div>
+
+		<div id="wrap-next-slide" class="slides">
+			<iframe src="/?receiver" width="640" height="512" id="next-slide"></iframe>
+			<span>UPCOMING:</span>
+		</div>
+		<div id="notes"></div>
+
+		<script src="/socket.io/socket.io.js"></script>
+
+		<script>
+		var socketId = '{{socketId}}';
+		var socket = io.connect(window.location.origin);
+		var notes = document.getElementById('notes');
+		var currentSlide = document.getElementById('current-slide');
+		var nextSlide = document.getElementById('next-slide');
+
+		socket.on('slidedata', function(data) {
+			// ignore data from sockets that aren't ours
+			if (data.socketId !== socketId) { return; }
+
+			notes.innerHTML = data.notes;
+			currentSlide.contentWindow.Reveal.navigateTo(data.indexh, data.indexv);
+			nextSlide.contentWindow.Reveal.navigateTo(data.nextindexh, data.nextindexv);
+		});
+		</script>
+
+	</body>
+</html>

Some files were not shown because too many files changed in this diff