This simple server app shows you how to use OpenTok Node Server SDK to create OpenTok sessions, generate tokens for those sessions, archive (or record) sessions, and download those archives.
Heroku is a PaaS (Platform as a Service) that can be used to deploy simple and small applications for free. To easily deploy this repository to Heroku, sign up for a Heroku account and click this button:
Heroku will prompt you to add your OpenTok API key and OpenTok API secret, which you can obtain at the TokBox Dashboard.
Railway is a deployment platform where you can provision infrastructure, develop with that infrastructure locally, and then deploy to the cloud.
Railway will prompt you to add your OpenTok API key and OpenTok API secret, which you can obtain at the TokBox Dashboard.
-
Clone the app by running the command
git clone [email protected]:opentok/learning-opentok-node.git
-
cd
to the root directory. -
Run
npm install
command to fetch and install all npm dependecies. -
Next, rename the
.envcopy
file located at the root directory to.env
, and enter in your TokBox api key and secret as indicated:# enter your TokBox api key after the '=' sign below TOKBOX_API_KEY= # enter your TokBox secret after the '=' sign below TOKBOX_SECRET=
-
Run
npm start
to start the app. -
Visit the URL http://localhost:8080/session in your browser. You should see a JSON response containing the OpenTok API key, session ID, and token.
The routes/index.js
file is the Express routing for the web service. The rest of this tutorial
discusses code in this file.
In order to navigate clients to a designated meeting spot, we associate the Session ID to a room name which is easier for people to recognize and pass. For simplicity, we use a local associated array to implement the association where the room name is the key and the Session ID is the value. For production applications, you may want to configure a persistence (such as a database) to achieve this functionality.
The GET /room/:name
route associates an OpenTok session with a "room" name. This route handles the passed room name and performs a check to determine whether the app should generate a new session ID or retrieve a session ID from the local in-memory hash. Then, it generates an OpenTok token for that session ID. Once the API key, session ID, and token are ready, it sends a response with the body set to a JSON object containing the information.
if (localStorage[roomName]) {
// fetch an existing sessionId
const sessionId = localStorage[roomName];
// generate token
token = opentok.generateToken(sessionId);
res.setHeader('Content-Type', 'application/json');
res.send({
apiKey: apiKey,
sessionId: sessionId,
token: token,
});
} else {
// Create a session that will attempt to transmit streams directly between
// clients. If clients cannot connect, the session uses the OpenTok TURN server:
opentok.createSession({ mediaMode: 'relayed' }, function (err, session) {
if (err) {
console.log(err);
res.status(500).send({ error: 'createSession error:', err });
return;
}
// store into local
localStorage[roomName] = session.sessionId;
// generate token
token = opentok.generateToken(session.sessionId);
res.setHeader('Content-Type', 'application/json');
res.send({
apiKey: apiKey,
sessionId: session.sessionId,
token: token,
});
});
}
The GET /session
routes generates a convenient session for fast establishment of communication.
router.get('/session', function (req, res, next) {
res.redirect('/room/session');
});
Start an Archive
A POST
request to the /archive/start
route starts an archive recording of an OpenTok session.
The session ID OpenTok session is passed in as JSON data in the body of the request
router.post('/archive/start', function (req, res, next) {
const json = req.body;
const sessionId = json['sessionId'];
opentok.startArchive(sessionId, { name: roomName }, function (err, archive) {
if (err) {
console.log(err);
res.status(500).send({ error: 'startArchive error:', err });
return;
}
res.setHeader('Content-Type', 'application/json');
res.send(archive);
});
});
You can only create an archive for sessions that have at least one client connected. Otherwise, the app will respond with an error.
A POST
request to the /archive:archiveId/stop
route stops an archive recording.
The archive ID is returned by call to the archive/start
endpoint.
router.post('/archive/:archiveId/stop', function (req, res, next) {
var archiveId = req.params.archiveId;
console.log('attempting to stop archive: ' + archiveId);
opentok.stopArchive(archiveId, function (err, archive) {
if (err) {
console.log(err);
res.status(500).send({ error: 'stopArchive error:', err });
return;
}
res.setHeader('Content-Type', 'application/json');
res.send(archive);
});
});
A GET
request to '/archive/:archiveId/view'
redirects the requested clients to
a URL where the archive gets played.
router.get('/archive/:archiveId/view', function (req, res, next) {
var archiveId = req.params.archiveId;
console.log('attempting to view archive: ' + archiveId);
opentok.getArchive(archiveId, function (err, archive) {
if (err) {
console.log(err);
res.status(500).send({ error: 'viewArchive error:', err });
return;
}
if (archive.status == 'available') {
res.redirect(archive.url);
} else {
res.render('view', { title: 'Archiving Pending' });
}
});
});
A GET
request to /archive/:archiveId
returns a JSON object that contains all archive properties, including status
, url
, duration
, etc. For more information, see here.
router.get('/archive/:archiveId', function (req, res, next) {
var sessionId = req.params.sessionId;
var archiveId = req.params.archiveId;
// fetch archive
console.log('attempting to fetch archive: ' + archiveId);
opentok.getArchive(archiveId, function (err, archive) {
if (err) {
console.log(err);
res.status(500).send({ error: 'infoArchive error:', err });
return;
}
// extract as a JSON object
res.setHeader('Content-Type', 'application/json');
res.send(archive);
});
});
A GET
request to /archive
with optional count
and offset
params returns a list of JSON archive objects. For more information, please check here.
Examples:
GET /archive // fetch up to 1000 archive objects
GET /archive?count=10 // fetch the first 10 archive objects
GET /archive?offset=10 // fetch archives but first 10 archive objetcs
GET /archive?count=10&offset=10 // fetch 10 archive objects starting from 11st
Start Captions
A POST
request to the /captions/start
route starts caption transcribing of an OpenTok session.
The session ID and a token is passed in as JSON data in the body of the request.
router.post('/captions/start', async function (req, res) {
// With custom expiry (Default 30 days)
const expires = Math.floor(new Date() / 1000) + (24 * 60 * 60);
const projectJWT = projectToken(apiKey, secret, expires);
const captionURL = `${captionsUrl}/${apiKey}/captions`;
const captionPostBody = {
sessionId: req.body.sessionId,
token: req.body.token,
languageCode: 'en-US',
partialCaptions: 'true',
};
try {
captionResponse = await axios.post(captionURL, captionPostBody, {
headers: {
'X-OPENTOK-AUTH': projectJWT,
'Content-Type': 'application/json',
},
});
} catch (err) {
console.warn(err);
res.status(500);
res.send(`Error starting transcription services: ${err}`);
return;
}
res.send(captionResponse.data.captionsId);
});
Stop Captions
A POST
request to the /captions/:captionsId/stop
route stops caption transcribing of an OpenTok session.
The captionsID is passed in as a parameter in the URL.
router.post('/captions/:captionsId/stop', postBodyParser, async (req, res) => {
const captionsId = req.params.captionsId;
// With custom expiry (Default 30 days)
const expires = Math.floor(new Date() / 1000) + (24 * 60 * 60);
const projectJWT = projectToken(apiKey, secret, expires);
const captionURL = `${opentokUrl}/${apiKey}/captions/${captionsId}/stop`;
try {
const captionResponse = await axios.post(captionURL, {}, {
headers: {
'X-OPENTOK-AUTH': projectJWT,
'Content-Type': 'application/json',
},
});
res.sendStatus(captionResponse.status);
} catch (err) {
console.warn(err);
res.status(500);
res.send(`Error stopping transcription services: ${err}`);
return;
}
});
This sample app does not provide client-side OpenTok functionality (for connecting to OpenTok sessions and for publishing and subscribing to streams). It is intended to be used with the OpenTok tutorials for Web, iOS, iOS-Swift, or Android:
Interested in contributing? We ❤️ pull requests! See the Contribution guidelines.
We love to hear from you so if you have questions, comments or find a bug in the project, let us know! You can either:
- Open an issue on this repository
- See https://support.tokbox.com/ for support options
- Tweet at us! We're @VonageDev on Twitter
- Or join the Vonage Developer Community Slack
- Check out the Developer Documentation at https://tokbox.com/developer/