Smartphone Remote Control with Node.js and Socket.io

Demo Download

Wouldn't it be cool to use your smartphone as a remote control? It turns out it is not difficult at all! You don't even need to know how to write native mobile apps - your phone has a fully capable web browser with support for web sockets, which opens a lot of possibilities. In this short tutorial, we are going to use Node.js and Socket.io to remotely control a presentation that is running on your laptop with your phone.

There are a lot of cool HTML5 presentation libraries out there and it would be a waste of effort to create this functionality from scratch. This is why we will be using Reveal.js - it will handle animations and transitions between slides, as well as support for keyboard and touch events.

We won't be making a dedicated remote control interface. Instead, we will synchronize the presentation that is opened on your phone with that on your computer using websockets. This will allow you not only to control the presentation, but see its mobile version on your phone, which will help you keep track of the current slide.

The Idea

The technique we are going to use is very simple. Reveal.js puts the current slide number in the URL as a hash (e.g. http://example.com/#/1). We will send this hash to all other connected devices, which will transition them to the new slide automatically. This will make it possible for people to just open the URL in a browser and sabotaging your presentation, so we will require all devices to enter a pass code before connecting.

It is worth mentioning that Reveal.js already has an API, so we could have used that to synchronize the two presentations. But the hash change technique is simpler and will work with any presentation library, so we chose to go with it instead.

rsz_slideshow.jpg
Our slideshow

Running our Example

You can run this example locally, or by deploying it to a hosting provider with node.js support like Heroku. Running it locally is easier, but you must have node.js and npm installed. Running it on Heroku requires you to have the heroku toolbelt installed and signing up for an account.

To run our code locally:

  1. Download the code from the button near the beginning of the article.
  2. Make sure you have node.js installed. If needed, install it.
  3. Unzip the archive you downloaded to a folder.
  4. Open a terminal and cd to the folder.
  5. Run npm install to install the required libraries
  6. Run node app.js to start the presentation
  7. Open http://localhost:8080 on your computer and enter your pass key (by default it is "kittens").
  8. Open http://<your computer's local ip address> on your phone and enter the same pass key.
  9. Have fun!

To run the code on Heroku:

  1. Download the code from the button near the beginning of the article.
  2. Unzip it to a folder.
  3. Open a terminal and cd to the folder.
  4. Create a git repository, and commit.
  5. Create a new Heroku App
  6. Run git push heroku master.
  7. Visit the URL of the app on every device you want to connect. The default pass key is "kittens".

Read more about deploying node.js apps to heroku here. If you use a different cloud hosting provider, the last three steps will be different.

The Code

But enough talk, let's see the code! There are only two JavaScript files - app.js for the server side, and script.js for the browser. You can run the app in Node.js 0.10+ or in io.js.

For the backend, we use express and socket.io. It's main responsibility is listening for and responding to socket.io events. With express.static, we serve the files in the public folder to the world. We have a public/index.html file which contains the code for the presentation. It is served automatically by express.static, so we don't need a "/" route.

app.js

// This is the server-side file of our mobile remote controller app.
// It initializes socket.io and a new express instance.
// Start it by running 'node app.js' from your terminal.

// Creating an express server

var express = require('express'),
    app = express();

// This is needed if the app is run on heroku and other cloud providers:

var port = process.env.PORT || 8080;

// Initialize a new socket.io object. It is bound to 
// the express app, which allows them to coexist.

var io = require('socket.io').listen(app.listen(port));

// App Configuration

// Make the files in the public folder available to the world
app.use(express.static(__dirname + '/public'));

// This is a secret key that prevents others from opening your presentation
// and controlling it. Change it to something that only you know.

var secret = 'kittens';

// Initialize a new socket.io application

var presentation = io.on('connection', function (socket) {

    // A new client has come online. Check the secret key and 
    // emit a "granted" or "denied" message.

    socket.on('load', function(data){

        socket.emit('access', {
            access: (data.key === secret ? "granted" : "denied")
        });

    });

    // Clients send the 'slide-changed' message whenever they navigate to a new slide.

    socket.on('slide-changed', function(data){

        // Check the secret key again

        if(data.key === secret) {

            // Tell all connected clients to navigate to the new slide

            presentation.emit('navigate', {
                hash: data.hash
            });
        }
    });
});

console.log('Your presentation is running on http://localhost:' + port);

And here is our JavaScript for the front-end, which listens for hashchange events and sends socket.io messages to the server.

public/assets/js/script.js

$(function() {

    // Apply a CSS filter with our blur class (see our assets/css/styles.css)

    var blurredElements = $('.homebanner, div.reveal').addClass('blur');

    // Initialize the Reveal.js library with the default config options
    // See more here https://github.com/hakimel/reveal.js#configuration

    Reveal.initialize({
        history: true       // Every slide will change the URL
    });

    // Connect to the socket

    var socket = io();

    // Variable initialization

    var form = $('form.login'),
        secretTextBox = form.find('input[type=text]');

    var key = "", animationTimeout;

    // When the page is loaded it asks you for a key and sends it to the server

    form.submit(function(e){

        e.preventDefault();

        key = secretTextBox.val().trim();

        // If there is a key, send it to the server-side
        // through the socket.io channel with a 'load' event.

        if(key.length) {
            socket.emit('load', {
                key: key
            });
        }

    });

    // The server will either grant or deny access, depending on the secret key

    socket.on('access', function(data){

        // Check if we have "granted" access.
        // If we do, we can continue with the presentation.

        if(data.access === "granted") {

            // Unblur everything
            blurredElements.removeClass('blurred');

            form.hide();

            var ignore = false;

            $(window).on('hashchange', function(){

                // Notify other clients that we have navigated to a new slide
                // by sending the "slide-changed" message to socket.io

                if(ignore){
                    // You will learn more about "ignore" in a bit
                    return;
                }

                var hash = window.location.hash;

                socket.emit('slide-changed', {
                    hash: hash,
                    key: key
                });
            });

            socket.on('navigate', function(data){

                // Another device has changed its slide. Change it in this browser, too:

                window.location.hash = data.hash;

                // The "ignore" variable stops the hash change from
                // triggering our hashchange handler above and sending
                // us into a never-ending cycle.

                ignore = true;

                setInterval(function () {
                    ignore = false;
                },100);

            });

        }
        else {

            // Wrong secret key

            clearTimeout(animationTimeout);

            // Addding the "animation" class triggers the CSS keyframe
            // animation that shakes the text input.

            secretTextBox.addClass('denied animation');

            animationTimeout = setTimeout(function(){
                secretTextBox.removeClass('animation');
            }, 1000);

            form.show();
        }

    });

});

It's Slideshow Time!

With this our smartphone remote control is ready! We hope that you find our experiment useful and have fun playing with it.

Bootstrap Studio

The revolutionary web design tool for creating responsive websites and apps.

Learn more

Related Articles

Webarranco

Thanks for this article !

I often use reveal for my presentations so it will be a real help !

I've tested your code and it didn't worked well. I don't know if you forgot to add the for the secret on the index.html, but for me, it didn't ask for the password. I copied the form from the index.html~ to index.html and it worked well :)

Thx again !

Martin Angelov

Sorry for the trouble! We've uploaded a broken index.html by mistake. It is now fixed - you can re-download the zip.

Maybe the problem is with me... but I followed the steps for the local setup, and after running node app.js, the app starts, and when I open the site on http://localhost:8080/ the site shows up, all blurry but the pass field doesn't show up, like in your heroku demo. Maybe the problem is, I'm running windows... wouldn't be the first time...

Martin Angelov

Nope, it was our mistake. You can download the zip again.

Hi.
Thanks for this good tutorial.
can you please tell us how to show video, powerpoint, pdf ..etc

Thank you a gain.

Martin Angelov

Reveal.js supports embedding videos and other types of media. You can read more here.

Mohammed Haashir

Hello

Is it possible to have a text box in the presentation which both the peers(the one running on smartphone and the one on the laptop/PC) can edit so it'll be like a notepad which both can edit and see working in real time? I mean can interaction take place than just navigation between pages? if so what do I need to edit/add?

Regards
Haashir

Florian Lutz

Hello

thanks a lot for this amazing tutorial.
I have a question regarding the secret key and the function to send the current slide to all other connected devices. I want to create a site which each user can swipe his own pages, so the other current users don't control the site of the others. Is this possible?

I would be very grateful for tips.

Florian Lutz

Martin Angelov

It is possible, but will be a lot of work. You can use Socket.io's rooms, and create a room for every presentation and assign a unique URL for it (for example http://example.com/presentations?id=abcdef1234). The difficult part will be creating an interface for uploading or building presentations. You may also need to add user accounts to make it more secure.

Matheus Santos

This is so much nice! It is a very interesting and useful implementation using socket.io. Thank you very much for sharing this knowledge with us!

Great Article. Thanks for posting.

Love the result, I will use this for my conference tool, with controls only for admin :-)
Amazing job !!

David Selden-Treiman

Hi Nick. Really interesting tutorial! I found it useful and engaging as I was learning Node.js. I have added it to my sites' list of Node.js tutorials to recommend to my visitors. Thank you very much for the great resource!

Felipe Marques

Congratulations!! Very nice tutorial... excelent!!

Cool, it works! BUT what about fragments? It seem that are not supported by this solution. Anyway good article

Hey, have you found a solution? I am using also fragments.

Is there any support for fragments? I would love to use this for our presentation to our clients but we use a lot of fragments.

ShauneGreenidge

Hi, a little help please.

I have followed the steps for the local setup, and after running node app.js, the app starts, and when I open the site on http://localhost:8080/ the site shows up, however, when i try to open the site on my android device (Galaxy Note 2) it says (in the browser) "This site cant be reached" http://...is unreachable.

Please, please help.

@ShauneGreenidge you're probably using the wrong url, you need to find your local ip (on mac: ipconfig getifaddr en0, add the 8080 port number and it should work