Skip to content

Commit 976536d

Browse files
committed
Added multiplexing server
1 parent 79299d0 commit 976536d

9 files changed

Lines changed: 285 additions & 30 deletions

File tree

README.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,16 @@ By default, the slides will be served at [localhost:1947](http://localhost:1947)
157157

158158
You can change the appearance of the speaker notes by editing the file at `plugin/speakernotes/notes.html`.
159159

160+
### Multiplexing
161+
162+
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)
163+
164+
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)
165+
166+
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.
167+
168+
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.
169+
160170
### Known Issues
161171

162172
- 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.
@@ -286,4 +296,4 @@ You can change the appearance of the speaker notes by editing the file at `plugi
286296

287297
MIT licensed
288298

289-
Copyright (C) 2012 Hakim El Hattab, http://hakim.se
299+
Copyright (C) 2012 Hakim El Hattab, http://hakim.se

index.html

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,15 @@ <h3 class="inverted">BY Hakim El Hattab / hakim.se</h3>
278278
<script src="lib/js/head.min.js"></script>
279279

280280
<script>
281+
282+
var multiplex = {
283+
id: null
284+
, secret: null
285+
, url: 'revealjs.jit.su'
286+
};
287+
288+
var notes = false;
289+
281290
head.ready( function() {
282291

283292
// Fires when a slide with data-state=customevent is activated
@@ -326,11 +335,20 @@ <h3 class="inverted">BY Hakim El Hattab / hakim.se</h3>
326335

327336
// If we're runnning the notes server we need to include some additional JS
328337
// TODO Is there a better way to determine if we're running the notes server?
329-
if( window.location.host === 'localhost:1947' ) {
338+
if( window.location.host === 'localhost:1947' || notes === true) {
330339
scripts.push( 'socket.io/socket.io.js' );
331340
scripts.push( 'plugin/speakernotes/client.js' );
332341
}
333342

343+
if( multiplex.id !== null ) {
344+
scripts.push( 'socket.io/socket.io.js' );
345+
scripts.push( 'plugin/multiplex/client.js' );
346+
347+
if( multiplex.secret !== null ) {
348+
scripts.push( 'plugin/multiplex/master.js' );
349+
}
350+
}
351+
334352
// Load the scripts and, when completed, initialize reveal.js
335353
head.js.apply( null, scripts );
336354

js/reveal.js

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -653,7 +653,7 @@ var Reveal = (function(){
653653
* Updates the visual slides to represent the currently
654654
* set indices.
655655
*/
656-
function slide( h, v ) {
656+
function slide( h, v, fireEvent ) {
657657
// Remember where we were at before
658658
previousSlide = currentSlide;
659659

@@ -720,12 +720,14 @@ var Reveal = (function(){
720720

721721
// Dispatch an event if the slide changed
722722
if( indexh !== indexhBefore || indexv !== indexvBefore ) {
723-
dispatchEvent( 'slidechanged', {
724-
'indexh': indexh,
725-
'indexv': indexv,
726-
'previousSlide': previousSlide,
727-
'currentSlide': currentSlide
728-
} );
723+
if( fireEvent !== false ) {
724+
dispatchEvent( 'slidechanged', {
725+
'indexh': indexh,
726+
'indexv': indexv,
727+
'previousSlide': previousSlide,
728+
'currentSlide': currentSlide
729+
} );
730+
}
729731
}
730732
else {
731733
// Ensure that the previous slide is never the same as the current
@@ -901,8 +903,8 @@ var Reveal = (function(){
901903
* @param {Number} h The horizontal index of the slide to show
902904
* @param {Number} v The vertical index of the slide to show
903905
*/
904-
function navigateTo( h, v ) {
905-
slide( h, v );
906+
function navigateTo( h, v, fireEvent ) {
907+
slide( h, v, fireEvent );
906908
}
907909

908910
function navigateLeft() {

package.json

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,30 @@
11
{
2-
"author": "Hakim El Hattab",
3-
"name": "reveal.js",
4-
"description": "HTML5 Slideware with Presenter Notes",
5-
"version": "1.5.0",
6-
"repository": {
7-
"type": "git",
8-
"url": "git://github.com/hakimel/reveal.js.git"
9-
},
10-
"engines": {
11-
"node": "~0.6.8"
12-
},
13-
"dependencies": {
14-
"underscore" : "1.3.3",
15-
"express" : "2.5.9",
16-
"socket.io" : "0.9.6",
17-
"mustache" : "0.4.0"
18-
},
19-
"devDependencies": {}
2+
"author": "Hakim El Hattab",
3+
"name": "reveal.js",
4+
"description": "HTML5 Slideware with Presenter Notes",
5+
"version": "1.5.1",
6+
"repository": {
7+
"type": "git",
8+
"url": "git://github.com/hakimel/reveal.js.git"
9+
},
10+
"engines": {
11+
"node": "0.8.*"
12+
},
13+
"scripts": {
14+
"start": "node ./plugin/multiplex/index.js"
15+
},
16+
"licenses": [
17+
{
18+
"type": "MIT",
19+
"url": "https://github.com/hakimel/reveal.js/raw/master/LICENSE"
20+
}
21+
],
22+
"dependencies": {
23+
"underscore": "1.3.3",
24+
"express": "2.5.9",
25+
"socket.io": "0.9.6",
26+
"mustache": "0.4.0"
27+
},
28+
"devDependencies": {},
29+
"subdomain": "revealjs"
2030
}

plugin/multiplex/client.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
(function() {
2+
var socketId = multiplex.id;
3+
var socket = io.connect(multiplex.url);
4+
5+
socket.on(multiplex.id, function(data) {
6+
// ignore data from sockets that aren't ours
7+
if (data.socketId !== socketId) { return; }
8+
if( window.location.host === 'localhost:1947' ) return;
9+
10+
Reveal.navigateTo(data.indexh, data.indexv, false);
11+
});
12+
}());

plugin/multiplex/index.js

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
var express = require('express');
2+
var fs = require('fs');
3+
var io = require('socket.io');
4+
var _ = require('underscore');
5+
var Mustache = require('mustache');
6+
var crypto = require('crypto');
7+
8+
var app = express.createServer();
9+
var staticDir = express.static;
10+
11+
io = io.listen(app);
12+
13+
var opts = {
14+
port : 1948,
15+
baseDir : __dirname + '/../../'
16+
};
17+
18+
io.sockets.on('connection', function(socket) {
19+
socket.on('slidechanged', function(slideData) {
20+
console.log(slideData);
21+
if (createHash(slideData.secret) === slideData.socketId) {
22+
slideData.secret = null;
23+
socket.broadcast.emit(slideData.socketId, slideData);
24+
};
25+
});
26+
});
27+
28+
app.configure(function() {
29+
[ 'css', 'js', 'plugin', 'lib' ].forEach(function(dir) {
30+
app.use('/' + dir, staticDir(opts.baseDir + dir));
31+
});
32+
});
33+
34+
app.get("/", function(req, res) {
35+
fs.createReadStream(opts.baseDir + '/index.html').pipe(res);
36+
});
37+
38+
app.get("/token", function(req,res) {
39+
var ts = new Date().getTime();
40+
var rand = Math.floor(Math.random()*9999999);
41+
var secret = ts.toString() + rand.toString();
42+
res.send({secret: secret, socketId: createHash(secret)});
43+
});
44+
45+
var createHash = function(secret) {
46+
var cipher = crypto.createCipher('blowfish', secret);
47+
return(cipher.final('hex'));
48+
};
49+
50+
// Actually listen
51+
app.listen(opts.port || null);
52+
53+
var brown = '\033[33m',
54+
green = '\033[32m',
55+
reset = '\033[0m';
56+
57+
var slidesLocation = "http://localhost" + ( opts.port ? ( ':' + opts.port ) : '' );
58+
59+
console.log( brown + "reveal.js - Speaker Notes" + reset );
60+
console.log( "1. Open the slides at " + green + slidesLocation + reset );
61+
console.log( "2. Click on the link your JS console to go to the notes page" );
62+
console.log( "3. Advance through your slides and your notes will advance automatically" );

plugin/multiplex/master.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
(function() {
2+
// don't emit events from inside the previews themselves
3+
if ( window.location.search.match( /receiver/gi ) ) { return; }
4+
5+
var socket = io.connect(multiplex.url);
6+
7+
Reveal.addEventListener( 'slidechanged', function( event ) {
8+
var nextindexh;
9+
var nextindexv;
10+
var slideElement = event.currentSlide;
11+
12+
if (slideElement.nextElementSibling && slideElement.parentNode.nodeName == 'SECTION') {
13+
nextindexh = event.indexh;
14+
nextindexv = event.indexv + 1;
15+
} else {
16+
nextindexh = event.indexh + 1;
17+
nextindexv = 0;
18+
}
19+
20+
var notes = slideElement.querySelector('aside.notes');
21+
var slideData = {
22+
indexh : event.indexh,
23+
indexv : event.indexv,
24+
nextindexh : nextindexh,
25+
nextindexv : nextindexv,
26+
secret: multiplex.secret,
27+
socketId : multiplex.id
28+
};
29+
30+
socket.emit('slidechanged', slideData);
31+
} );
32+
}());

plugin/multiplex/notes.html

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
6+
<title>reveal.js - Slide Notes</title>
7+
8+
<style>
9+
body {
10+
font-family: Helvetica;
11+
}
12+
13+
#notes {
14+
font-size: 24px;
15+
width: 640px;
16+
margin-top: 5px;
17+
}
18+
19+
#wrap-current-slide {
20+
width: 640px;
21+
height: 512px;
22+
float: left;
23+
overflow: hidden;
24+
}
25+
26+
#current-slide {
27+
width: 1280px;
28+
height: 1024px;
29+
border: none;
30+
-moz-transform: scale(0.5);
31+
-moz-transform-origin: 0 0;
32+
-o-transform: scale(0.5);
33+
-o-transform-origin: 0 0;
34+
-webkit-transform: scale(0.5);
35+
-webkit-transform-origin: 0 0;
36+
}
37+
38+
#wrap-next-slide {
39+
width: 320px;
40+
height: 256px;
41+
float: left;
42+
margin: 0 0 0 10px;
43+
overflow: hidden;
44+
}
45+
46+
#next-slide {
47+
width: 1280px;
48+
height: 1024px;
49+
border: none;
50+
-moz-transform: scale(0.25);
51+
-moz-transform-origin: 0 0;
52+
-o-transform: scale(0.25);
53+
-o-transform-origin: 0 0;
54+
-webkit-transform: scale(0.25);
55+
-webkit-transform-origin: 0 0;
56+
}
57+
58+
.slides {
59+
position: relative;
60+
margin-bottom: 10px;
61+
border: 1px solid black;
62+
border-radius: 2px;
63+
background: rgb(28, 30, 32);
64+
}
65+
66+
.slides span {
67+
position: absolute;
68+
top: 3px;
69+
left: 3px;
70+
font-weight: bold;
71+
font-size: 14px;
72+
color: rgba( 255, 255, 255, 0.9 );
73+
}
74+
</style>
75+
</head>
76+
77+
<body>
78+
79+
<div id="wrap-current-slide" class="slides">
80+
<iframe src="/?receiver" width="1280" height="1024" id="current-slide"></iframe>
81+
</div>
82+
83+
<div id="wrap-next-slide" class="slides">
84+
<iframe src="/?receiver" width="640" height="512" id="next-slide"></iframe>
85+
<span>UPCOMING:</span>
86+
</div>
87+
<div id="notes"></div>
88+
89+
<script src="/socket.io/socket.io.js"></script>
90+
91+
<script>
92+
var socketId = '{{socketId}}';
93+
var socket = io.connect(window.location.origin);
94+
var notes = document.getElementById('notes');
95+
var currentSlide = document.getElementById('current-slide');
96+
var nextSlide = document.getElementById('next-slide');
97+
98+
socket.on('slidedata', function(data) {
99+
// ignore data from sockets that aren't ours
100+
if (data.socketId !== socketId) { return; }
101+
102+
notes.innerHTML = data.notes;
103+
currentSlide.contentWindow.Reveal.navigateTo(data.indexh, data.indexv);
104+
nextSlide.contentWindow.Reveal.navigateTo(data.nextindexh, data.nextindexv);
105+
});
106+
</script>
107+
108+
</body>
109+
</html>

plugin/speakernotes/client.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// don't emit events from inside the previews themselves
33
if ( window.location.search.match( /receiver/gi ) ) { return; }
44

5-
var socket = io.connect(window.location.origin);
5+
var socket = io.connect('127.0.0.1:1947');
66
var socketId = Math.random().toString().slice(2);
77

88
console.log('View slide notes at ' + window.location.origin + '/notes/' + socketId);

0 commit comments

Comments
 (0)