Added multiplexing server
This commit is contained in:
parent
79299d0071
commit
976536d15e
10
README.md
10
README.md
|
@ -157,6 +157,16 @@ By default, the slides will be served at [localhost:1947](http://localhost:1947)
|
||||||
|
|
||||||
You can change the appearance of the speaker notes by editing the file at `plugin/speakernotes/notes.html`.
|
You can change the appearance of the speaker notes by editing the file at `plugin/speakernotes/notes.html`.
|
||||||
|
|
||||||
|
### 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
|
### Known Issues
|
||||||
|
|
||||||
- The notes page is supposed to show the current slide and the next slide, but when it first starts, it always shows the first slide in both positions.
|
- The notes page is supposed to show the current slide and the next slide, but when it first starts, it always shows the first slide in both positions.
|
||||||
|
|
20
index.html
20
index.html
|
@ -278,6 +278,15 @@ function linkify( selector ) {
|
||||||
<script src="lib/js/head.min.js"></script>
|
<script src="lib/js/head.min.js"></script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
|
var multiplex = {
|
||||||
|
id: null
|
||||||
|
, secret: null
|
||||||
|
, url: 'revealjs.jit.su'
|
||||||
|
};
|
||||||
|
|
||||||
|
var notes = false;
|
||||||
|
|
||||||
head.ready( function() {
|
head.ready( function() {
|
||||||
|
|
||||||
// Fires when a slide with data-state=customevent is activated
|
// Fires when a slide with data-state=customevent is activated
|
||||||
|
@ -326,11 +335,20 @@ function linkify( selector ) {
|
||||||
|
|
||||||
// If we're runnning the notes server we need to include some additional JS
|
// If we're runnning the notes server we need to include some additional JS
|
||||||
// TODO Is there a better way to determine if we're running the notes server?
|
// TODO Is there a better way to determine if we're running the notes server?
|
||||||
if( window.location.host === 'localhost:1947' ) {
|
if( window.location.host === 'localhost:1947' || notes === true) {
|
||||||
scripts.push( 'socket.io/socket.io.js' );
|
scripts.push( 'socket.io/socket.io.js' );
|
||||||
scripts.push( 'plugin/speakernotes/client.js' );
|
scripts.push( 'plugin/speakernotes/client.js' );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if( multiplex.id !== null ) {
|
||||||
|
scripts.push( 'socket.io/socket.io.js' );
|
||||||
|
scripts.push( 'plugin/multiplex/client.js' );
|
||||||
|
|
||||||
|
if( multiplex.secret !== null ) {
|
||||||
|
scripts.push( 'plugin/multiplex/master.js' );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Load the scripts and, when completed, initialize reveal.js
|
// Load the scripts and, when completed, initialize reveal.js
|
||||||
head.js.apply( null, scripts );
|
head.js.apply( null, scripts );
|
||||||
|
|
||||||
|
|
|
@ -653,7 +653,7 @@ var Reveal = (function(){
|
||||||
* Updates the visual slides to represent the currently
|
* Updates the visual slides to represent the currently
|
||||||
* set indices.
|
* set indices.
|
||||||
*/
|
*/
|
||||||
function slide( h, v ) {
|
function slide( h, v, fireEvent ) {
|
||||||
// Remember where we were at before
|
// Remember where we were at before
|
||||||
previousSlide = currentSlide;
|
previousSlide = currentSlide;
|
||||||
|
|
||||||
|
@ -720,6 +720,7 @@ var Reveal = (function(){
|
||||||
|
|
||||||
// Dispatch an event if the slide changed
|
// Dispatch an event if the slide changed
|
||||||
if( indexh !== indexhBefore || indexv !== indexvBefore ) {
|
if( indexh !== indexhBefore || indexv !== indexvBefore ) {
|
||||||
|
if( fireEvent !== false ) {
|
||||||
dispatchEvent( 'slidechanged', {
|
dispatchEvent( 'slidechanged', {
|
||||||
'indexh': indexh,
|
'indexh': indexh,
|
||||||
'indexv': indexv,
|
'indexv': indexv,
|
||||||
|
@ -727,6 +728,7 @@ var Reveal = (function(){
|
||||||
'currentSlide': currentSlide
|
'currentSlide': currentSlide
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
// Ensure that the previous slide is never the same as the current
|
// Ensure that the previous slide is never the same as the current
|
||||||
previousSlide = null;
|
previousSlide = null;
|
||||||
|
@ -901,8 +903,8 @@ var Reveal = (function(){
|
||||||
* @param {Number} h The horizontal index of the slide to show
|
* @param {Number} h The horizontal index of the slide to show
|
||||||
* @param {Number} v The vertical index of the slide to show
|
* @param {Number} v The vertical index of the slide to show
|
||||||
*/
|
*/
|
||||||
function navigateTo( h, v ) {
|
function navigateTo( h, v, fireEvent ) {
|
||||||
slide( h, v );
|
slide( h, v, fireEvent );
|
||||||
}
|
}
|
||||||
|
|
||||||
function navigateLeft() {
|
function navigateLeft() {
|
||||||
|
|
24
package.json
24
package.json
|
@ -2,19 +2,29 @@
|
||||||
"author": "Hakim El Hattab",
|
"author": "Hakim El Hattab",
|
||||||
"name": "reveal.js",
|
"name": "reveal.js",
|
||||||
"description": "HTML5 Slideware with Presenter Notes",
|
"description": "HTML5 Slideware with Presenter Notes",
|
||||||
"version": "1.5.0",
|
"version": "1.5.1",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git://github.com/hakimel/reveal.js.git"
|
"url": "git://github.com/hakimel/reveal.js.git"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "~0.6.8"
|
"node": "0.8.*"
|
||||||
},
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "node ./plugin/multiplex/index.js"
|
||||||
|
},
|
||||||
|
"licenses": [
|
||||||
|
{
|
||||||
|
"type": "MIT",
|
||||||
|
"url": "https://github.com/hakimel/reveal.js/raw/master/LICENSE"
|
||||||
|
}
|
||||||
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"underscore" : "1.3.3",
|
"underscore": "1.3.3",
|
||||||
"express" : "2.5.9",
|
"express": "2.5.9",
|
||||||
"socket.io" : "0.9.6",
|
"socket.io": "0.9.6",
|
||||||
"mustache" : "0.4.0"
|
"mustache": "0.4.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {}
|
"devDependencies": {},
|
||||||
|
"subdomain": "revealjs"
|
||||||
}
|
}
|
||||||
|
|
12
plugin/multiplex/client.js
Normal file
12
plugin/multiplex/client.js
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
(function() {
|
||||||
|
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.navigateTo(data.indexh, data.indexv, false);
|
||||||
|
});
|
||||||
|
}());
|
62
plugin/multiplex/index.js
Normal file
62
plugin/multiplex/index.js
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
var express = require('express');
|
||||||
|
var fs = require('fs');
|
||||||
|
var io = require('socket.io');
|
||||||
|
var _ = require('underscore');
|
||||||
|
var Mustache = require('mustache');
|
||||||
|
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) {
|
||||||
|
console.log(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);
|
||||||
|
|
||||||
|
var brown = '\033[33m',
|
||||||
|
green = '\033[32m',
|
||||||
|
reset = '\033[0m';
|
||||||
|
|
||||||
|
var slidesLocation = "http://localhost" + ( opts.port ? ( ':' + opts.port ) : '' );
|
||||||
|
|
||||||
|
console.log( brown + "reveal.js - Speaker Notes" + reset );
|
||||||
|
console.log( "1. Open the slides at " + green + slidesLocation + reset );
|
||||||
|
console.log( "2. Click on the link your JS console to go to the notes page" );
|
||||||
|
console.log( "3. Advance through your slides and your notes will advance automatically" );
|
32
plugin/multiplex/master.js
Normal file
32
plugin/multiplex/master.js
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
(function() {
|
||||||
|
// don't emit events from inside the previews themselves
|
||||||
|
if ( window.location.search.match( /receiver/gi ) ) { return; }
|
||||||
|
|
||||||
|
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 notes = slideElement.querySelector('aside.notes');
|
||||||
|
var slideData = {
|
||||||
|
indexh : event.indexh,
|
||||||
|
indexv : event.indexv,
|
||||||
|
nextindexh : nextindexh,
|
||||||
|
nextindexv : nextindexv,
|
||||||
|
secret: multiplex.secret,
|
||||||
|
socketId : multiplex.id
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.emit('slidechanged', slideData);
|
||||||
|
} );
|
||||||
|
}());
|
109
plugin/multiplex/notes.html
Normal file
109
plugin/multiplex/notes.html
Normal file
|
@ -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>
|
|
@ -2,7 +2,7 @@
|
||||||
// don't emit events from inside the previews themselves
|
// don't emit events from inside the previews themselves
|
||||||
if ( window.location.search.match( /receiver/gi ) ) { return; }
|
if ( window.location.search.match( /receiver/gi ) ) { return; }
|
||||||
|
|
||||||
var socket = io.connect(window.location.origin);
|
var socket = io.connect('127.0.0.1:1947');
|
||||||
var socketId = Math.random().toString().slice(2);
|
var socketId = Math.random().toString().slice(2);
|
||||||
|
|
||||||
console.log('View slide notes at ' + window.location.origin + '/notes/' + socketId);
|
console.log('View slide notes at ' + window.location.origin + '/notes/' + socketId);
|
||||||
|
|
Loading…
Reference in New Issue
Block a user