OpenTok Sessions using Node.js

Problem

When developing an OpenTok application, one choice developers must make is to decide how to generate new sessions. Should all publishers get their own session? Should only X number of people be allowed in a given session? How do you decide what person goes in to what session?

Since there is no direct way of querying how many clients are in a given session (and who those clients are), it is difficult to implement session management logic. One solution is to track session/client information in a database. Before joining a session, you could query a database that stores client information, however constantly maintaining this database can become slow and complicated, and dealing with client disconnects is non-trivial.

Solution

The solution that I decided on was to use Node.js to create a simple HTTP and socket server that collects information about connected clients (who they are and how many are connected). With that information I can then generate sessions based on whatever logic I would like.

The server works by accepting an incoming client, processing the logic to determine which session the client should be placed in, and then sending all the relevant session data back to the client. This is a fast, scalable, and lightweight solution that broadcasts in real-time, doesn’t require a database, and is created entirely in JavaScript.

Tutorial Overview

In this tutorial I will discuss how I implemented the server using Node.js and the OpenTok Node.js SDK created by Brian Stoner.

Assumptions

This tutorial assumes that you are familiar with the OpenTok API. If you are not please first read through the Getting Started Guide.

This tutorial also assumes that you have installed Node.js and the node package manager. I will try to walk through each part without any assumed knowledge beyond the Hello World tutorial from Node.js. If you would like to learn more about using Node.js, I recommend reading this outstanding tutorial.

Use Case

The particular use case that this tutorial will solve is the following:

  • Each session can have a maximum number of 3 users
  • When a new user connects, the user is put in to the first session that is not full
  • If all sessions are full, a new session will be generated

While this tutorial uses the above logic to solve this particular use case, the techniques discussed here can be applied to several other cases.

Part One: Download Required Modules

The server uses three Node modules:

  • opentok: OpenTok Server-Side SDK used for generating sessions and tokens.
  • socket.io: Enables easy two-way, real-time connection between server and client.
  • connect: Middleware framework for adding features to HTTP server.

Install these modules using Node Package Manager with the following commands:
[code]
npm install opentok
npm install socket.io
npm install connect
[/code]

Once you have modules installed, download the project code from the github repo with this command:
[code]
git clone https://github.com/jonmumm/OpenTok-Node-Session.git
[/code]

Once you have the repository downloaded, cd to that directory and run this command to start the server:
[code]
node server.js
[/code]

This will start the server on port 3000. You should now be able to view the application at http://localhost:3000.

Notice that our node server is actually serving the HTTP content, it contains its own HTTP server. This is done in only a few lines of code. This is pretty cool.

I will go in to more detail of how this works, however, you should know that while node can serve our HTTP content, it does not have to. For our purposes, we could just as easily use an existing HTTP server and use the node server for doing other things (like for sockets, which is what we use it for).

The project consists of two files:

  • server.js: the node server-side code.
  • index.html: the client that connects to the node server.

I will go through both files and discuss how each works.

Part Two: Server-Side Code

The server-side code can be separated into two parts. The first part initializes the HTTP server, socket server, and event handlers and the second part implements the OpenTok session generation logic for my particular use case.

Here is the first part of the server:
[javascript]
// Import the Node modules we need
var connect = require(‘connect’),
io = require(‘socket.io’),
opentok = require(‘opentok’);

// Set up the HTTP server for serving our static files
var server = connect(
connect.static(__dirname + "/web")
);
server.listen(3000); // Start server on port 3000

// Set up Socket listener and initialize event handlers
var socket = io.listen(server);
socket.on(‘connection’, function(client) {

// When a client connects, figure out which session to join
getSession(client);

client.on(‘disconnect’, function() {
// When a client disconnects, drop that client from the session
leaveSession(client);
});
});
[/javascript]
First we import the required modules we need. Then we initialize the HTTP server. For that I am using the Connect middleware framework which allows you to quickly specify middleware modules to run on your HTTP server. In this case I am simply specifying what directory to serve the static content out of (i.e. my html and javascript files). For our purposes, using the HTTP server to serve your content is optional; you could serve your files elsewhere and just use the node server for sockets.

Once our HTTP server is created and listening on port 3000, we create the socket server. The socket server is what will allow us to send messages back and forth between client and server in real-time.

Notice that we pass the HTTP server as a parameter when initializing our socket. This makes it so that the HTTP and socket servers are both listening on the same port. However neither server takes over the other. What will happen is, any socket request will be automatically intercepted by the socket server, and all other requests will go to the HTTP server.

After our socket server is initialized, we set up event handlers for when a client connects and disconnects. Essentially what we are doing here is saying “when a client connects, get that client a session to connect to” and “when a client disconnects, remove that client from the session”.

Here we only set up event handlers for the “connection” and “disconnect” events from the client, however you could also set up a listener for the “message” event for when the client sends a message to the server, however we have no need for that here.

That is all the code you need for setting up the basics of the server. Now we will dive in to the logic for generating OpenTok sessions.

[javascript]
// OpenTok Variables
var OPENTOK_API_KEY = ‘413302’, // Replace with your API key
OPENTOK_API_SECRET = ‘fc512f1f3c13e3ec3f590386c986842f92efa7e7’, // Replace with your API secret

// OpenTok SDK
ot = new opentok.OpenTokSDK(OPENTOK_API_KEY, OPENTOK_API_SECRET),

// NOTE: Uncomment for production, defaults to "staging.tokbox.com"
// ot.setEnvironment("api.tokbox.com"),

// Variables for managing OpenTok Sessions
MAX_SESSION_CONNECTIONS = 3, // Maximum number of client connections we want in a given session
session_map = {}, // Hash for getting the session of a given client
ot_sessions = new Array(); // Array for holding all sessions we have generated

// Finds an available session for the client to connect to
function getSession(client) {

var session;
// Look through all sessions to find a session that has less than the max number of sessions
// NOTE: We start searching from the top of the array since it is more likely a non-full session is there
for (var i = ot_sessions.length – 1; i >= 0; i–) {
var tmp_session = ot_sessions[i];
if (tmp_session.clients.length < MAX_SESSION_CONNECTIONS) {
session = tmp_session;
break;
}
}

if (!session) {
// If we didn’t find a session, generate one and enter it
ot.createSession(‘localhost’,{},function(session) {
ot_sessions.push(session);
enterSession(session,client);
})
} else {
// Otherwise enter the session we found
enterSession(session, client);
}
}

// Sends the session info back to the client for the client to join
function enterSession(session, client) {
// Construct info object to pass back to client then send it
var opentok_info = {
sessionId: session.sessionId,
apiKey: OPENTOK_API_KEY,
token: ot.generateToken()
}
client.send(opentok_info);

// Create array to hold all the clients in the session
if (!session.clients) {
session.clients = new Array();
}

// Add the client to the session
session.clients.push(client.sessionId);
session_map[client.sessionId] = session; // Use map later to identify what session client was in
}

// Finds which session the client was in and removes the client from that session.
function leaveSession(client) {
// Find the session that the client was in
var session = session_map[client.sessionId];

// Find the position of the client in the session
var index = session.clients.indexOf(client.sessionId);

// Remove the client from the session
session.clients.splice(index, 1);
}
[/javascript]
All we are doing here is implementing the logic from the use case I described above. We first look to see if there are any sessions that have less than 3 people in it, if so we use the first session that does. Otherwise we generate a new session. Once we have the sessionId, we package it into a JSON object along with a token and the apiKey and send it through the socket to the client.

Part Three: Client-Side Code

Since the node server takes care of all the session logic, it makes our client implementation very simple.
[javascript]
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<title>OpenTok Node.js</title>

<script src="http://cdn.socket.io/stable/socket.io.js" type="text/javascript"></script>
<script src="http://jonmumm.github.com/OpenTok-JS-LayoutContainer/OT_LayoutContainer.js" type="text/javascript"></script>
<script src="http://staging.tokbox.com/v0.91/js/TB.min.js" type="text/javascript" ></script>
<script src="OT_Widget.js" type="text/javascript" ></script>

<script>
// Initialize the socket, set up event handler, and connect
var socket = new io.Socket(null, {port: 3000, rememberTransport: false});
socket.on("message",messageHandler);
socket.connect();

function messageHandler(message) {
var opentok_info = message;

OT_Widget.startWidget("widgetContainer", 700, 400,
opentok_info.apiKey, opentok_info.sessionId, opentok_info.token);
}
</script>
</head>

<body>
<div id="widgetContainer"></div>
</body>
</html>
[/javascript]
Here we connect to the socket server, listen for messages from it, and then use the message we receive to set up our OpenTok session. Very simple.

Notice here I use a class called OT_Widget which is just a wrapper for the example that uses the LayoutContainer class from my previous blog post on layout management.

Conclusion

Using Node.js with socket.io gives us a flexible option for implementing session generation logic. If you have other ideas on how to use Node.js with OpenTok, post your thoughts here or tweet them @tokboxdev.

Tutorial GitHub Repo
OpenTok Node.js SDK created by Brian Stoner.

Read More

Video Stream Layout Management

Problem

A common challenge developers face when creating video chat applications is how to lay out the video streams on the page in an elegant manner. Many video chat applications involve a variable number of streams, users are constantly joining and leaving the chat session—dealing with these changes so that the streams do not affect other elements on the page can be difficult.

Solution

One way of dealing with this challenge is to define a fixed-size container where you want all your streams to live and then dynamically resize the streams within the container so that all the streams can fit. This is the approach the TokBox team took when developing the OpenTok HTML Embed Widget. You can see this approach in action in the sample app we are going to build here.

Tutorial Overview

In this tutorial I will cover the LayoutContainer class, a javascript class created for the HTML Embed Widget which makes it simple for OpenTok developers to lay out a variable number of streams on the page in a way that minimizes empty space and does not affect other elements on the page. This tutorial assumes that you have a basic knowledge of how an OpenTok application works. If you do not, please first read the basic tutorial on how to get started.

The first part of this tutorial will go over the LayoutContainer class and explain its methods. The second part will go over usage of the LayoutContainer class within a simple OpenTok application. I suggest that you first try the LayoutContainer Sample app we are going to build to understand the problem and how this approach solves it before continuing.

Before we start, if you would like to download the LayoutContainer.js class and the sample code for what we are going to build, you can download both from the Github repository here.

The LayoutContainer Class

The LayoutContainer class is made up of four methods. I will go through each of these four methods and explain when and how to use them.

LayoutContainer.init(divId, width, height)

The LayoutContainer.init() method initializes the div where you want your streams to be displayed and should be called once at the beginning of your application. This method takes as parameters the div id of the div to be used to manage the streams, and the height and width that you would like that div to be (Note: you do not need to set this height and width in the markup or CSS, the LayoutContainer class will do it for you).

LayoutContainer.addStream(divId, publisher)

The LayoutContainer.addStream() method adds a stream to be managed by the LayoutContainer. This method will usually be called right before you subscribe or publish a stream. This method takes as parameters the id that you pass in to the Session.publish() or Session.subscribe() methods and also a boolean variable publisher that is true if the id you are passing in is from a publisher or false if the id is from a subscriber.

LayoutContainer.removeStream(subscriberId)

The LayoutContainer.removeStream() method removes a stream from being managed by the LayoutContainer. This method takes one parameter, the subscriber id of the subscriber from the stream to be removed.

LayoutContainer.layout()

The LayoutContainer.layout() method lays out the LayoutContainer to display changes from any streams that were added or removed. This method calculates the optimal row and column configuration to use to arrange the videos to minimize the amount of wasted space within the container (i.e. makes each stream as large as possible without going beyond the edge of the LayoutContainer). This method should usually be called after you have added or removed streams from the LayoutContainer to update the display.

Using the LayoutContainer Class

The first thing we must do is include the appropriate JavaScript files. We are going to include the LayoutContainer.js for the LayoutContainer and TB.min.js which is the OpenTok JavaScript API.

[javascript]
<script src="OT_LayoutContainer.js" type="text/javascript" charset="utf-8"></script>
<script src="http://staging.tokbox.com/v0.91/js/TB.min.js" type="text/javascript" charset="utf-8"></script>
[/javascript]

Next we will initialize the session object and set up the event handlers we need.
[javascript]
var apiKey = 1127; // OpenTok sample API key. Replace with your own API key.
var sessionId = ‘287a9e4ad0aa8e501309df11fe53831452fa1167’; // Replace with your session ID.
var token = ‘devtoken’; // Should not be hard-coded.

session = TB.initSession(sessionId);

session.addEventListener(‘sessionConnected’, sessionConnectedHandler);
session.addEventListener(‘streamCreated’, streamCreatedHandler);
session.addEventListener(‘streamDestroyed’, streamDestroyedHandler);
[/javascript]

We initialize our layout container (and we have a div with id “streamContainer” in the HTML). In this example I picked a resolution of 500px by 340px—you can adjust this to whatever size you would like your container to be.
[javascript]
OT_LayoutContainer.init("streamContainer", 500, 340);
[/javascript]

Now before we define our sessionConnected, streamCreated, and streamDestroyed event handlers, we are going to create two helper methods for publishing and subscribing to streams.
[javascript]
function publishStream() {
// Make up an id for our publisher
var divId = ‘opentok_publisher’;

// Pass in TRUE since this is a publisher
OT_LayoutContainer.addStream(divId, true);

session.publish(divId);
}
[/javascript]
In the previous method we define an id, pass it to the LayoutContainer which will create the div for us and manage it for us, and then publish with it.

[javascript]
function subscribeToStreams(streams) {
// For each stream
for (var i = 0; i < streams.length; i++) {
// Check if this is the stream that I am publishing, and if so do not subscribe.
if (streams[i].connection.connectionId != session.connection.connectionId) {
// Make a unique div id for this stream
var divId = ‘stream_’ + streams[i].streamId;

// Pass in FALSE since this is a subscriber
OT_LayoutContainer.addStream(divId, false);

session.subscribe(streams[i], divId);
}
}
}
[/javascript]
In the previous method we take an array of streams, define unique ids for each one, and then again pass those ids to the LayoutContainer which takes care of creating and managing the divs for us. Finally, we subscribe to each stream.

Now that we have our helper methods set up and the session and LayoutContainer initialized, all that is left to do is set up our sessionConnected, streamCreated, and streamDestroyed event handlers.

[javascript]
function sessionConnectedHandler(event) {
// Publish my stream to the session
publishStream();

// Subscribe to all streams currently in the Session
subscribeToStreams(event.streams);

// Re-layout the container with the new streams
OT_LayoutContainer.layout();
}

function streamCreatedHandler(event) {
// Subscribe to the newly created streams
subscribeToStreams(event.streams);

// Re-layout the container with the new streams
OT_LayoutContainer.layout();
}

function streamDestroyedHandler(event) {
// Get all destroyed streams
for (var i = 0; i < event.streams.length; i++) {
// For each stream get the subscriber to that stream
var subscribers = session.getSubscribersForStream(event.streams[i]);
for (var j = 0; j < subscribers.length; j++) {
// Then remove each stream
OT_LayoutContainer.removeStream(subscribers[j].id);
}
}

// Re-layout the container without the removed streams
OT_LayoutContainer.layout();

}
[/javascript]
These event handlers are pretty straight-forward. Whenever somebody connects they first publish and then subscribe to all existing streams. We subscribe to new streams when they are created and remove streams whenever they are destroyed. Notice that we call OT_LayoutContainer.layout() at the end of each event handler to update the display of the LayoutContainer with changes.

Now when we connect to the session we will have a div that contains all our streams. As people join and leave the session, the streams within the container will re-arrange themselves to make the most efficient use of the available space. Since all streams are contained within a fixed-size div, we do not need to worry about how the the varying number of streams might affect the rest of the page.

Conclusion

The LayoutContainer class is a simple solution for solving a common problem when it comes to video chat applications. Using this class is one solution, but it is certainly not the only one. If you have any ideas or are interested in hacking together your own classes and modules for other OpenTok developers to use, I would love you to post your thoughts here or tweet them @tokboxdev.

The LayoutContainer class, sample application, and documentation is available for download here.

Read More

Evangelize Me

Greetings, all. My name is Jon and I’m the first Developer Evangelist to join the TokBox team.  This is an exciting opportunity for me because I genuinely believe that OpenTok gives developers the flexibility to build web applications that use video chat in creative and interesting new ways.  Me being able to become a direct part of that is thrilling.

As the Developer Evangelist, my role will be to both be the representative to developers on behalf of TokBox, and the representative to TokBox on behalf of developers. This means I will:

  • Be out and about meeting developers and spreading the word about OpenTok.
  • Listen to feedback from developers on how we can improve OpenTok.
  • Create series of tutorials, blogs, videos, and podcasts on how to use and make the most out of the OpenTok API.
  • Assist developers in getting their apps from ideation to real-world implementation.

I wholeheartedly look forward to working with all of you in the developer community to build interesting and useful new products using the OpenTok API. You reach me on Twitter or LinkedIn.  Very excited for this all!

Read More