A small demo application demonstrating usage of OpenTok video embeds in appointments.
OpenTok video embeds are simple embeddable widgets that can be added to web pages to get ready-made video conference with upto 3 participants. Embeds support dynamic rooms by changing a single URL parameter. This makes it ideal for using it in simple use cases without requiring too much programming. This demo is a small application that demonstrates using dynamic embed rooms along with scheduled meetings between one "doctor" and one "patient".
This demo does not require using any of OpenTok SDKs because all it uses are code snippets for OpenTok video embeds. The application is just a proof-of-concept to demonstrate that video embeds can be used for interesting purposes.
Note: This branch contains a simple version of the demo, with in-memory database. Check the database
branch for an example and walkthrough with PostgreSQL as the database backend, basic user authentication and multiple doctor/patient support.
See TUTORIAL.md for a step-by-step tutorial on building such an application.
See INSTALL.md for installation instruction and first time setup.
This is a step-by-step walkthrough of building the demo, highlighting the important pieces.
This sample application is modelled after a basic telehealth use case, with patients meeeting doctors online. The same can be edited and applied to other similar 1:1 use cases, like tutor:student or agent:customer.
In this example, a doctor can create meetings for time slots when they are available. Patients can then find and book an available appointment. At the time of the appointment, patients and doctors are connected together to a meeting room. The meeting room loads an existing OpenTok embed code with a custom room name, ensuring each meeting happens in a different room.
- Enter as Doctor
- Creates meetings for the times when they are available
- Doctor's dashboard shows upcoming meeetings
- Doctor can click on corresponding meeting link to join the meeting.
- Once on the meeting page, doctor clicks "Start Call" button to join.
- Once call duration is over, page reloads mentioning meeting is over.
- Enter as Patient
- Searches for available appointment slots and books the one that they want.
- Patient's dashboard shows upcoming meeetings
- Patient can click on corresponding meeting link to join the meeting.
- Once on the meeting page, patient clicks "Start Call" button to join.
- Once call duration is over, page reloads mentioning meeting is over.
- Application backend: NodeJS 6.9+
First up, install NodeJS and npm
.
Once all of these are ready, create a directory and initiate a npm
project:
$ mkdir -p opentok-video-embed-demo/{bin,routes,static,views}
$ cd opentok-video-embed-demo
$ npm init -y
This will create the directory structure we will use for the project and initiate a package.json
file in the current directory with default values. Edit this file to tweak the version
and description
fields as needed.
Then, install the required NodeJS module dependencies:
$ npm install --save express ejs express-session body-parser cookie-parser
Note: The rest of the tutorial contains relevant code sections for each file. For full code, go through the entire file in the repository.
This demo stores everything in memory to make things simple. The db.js
script exports an object, DB
, which stores meetings and embed code and gives few useful methods to query the meetings
array.
app.js
adds the DB
object to global
scope so that the rest of the application can access it.
This how how DB
looks like:
// This is our simple DB in memory. A real-world use case would use an actual database.
let DB = {
// Used to store meeting information
meetings: [],
// Used to store embed code
embed_code: ""
};
Each meeting entry in DB.meetings[]
uses this data structure:
{
id: number,
start_time: Date,
end_time: Date,
booked: false
}
When a patient books a meeting the booked
property is set to true
. This is how a sample meeting entry looks like:
{
id: 3,
start_time: 2017-08-04T13:51:00.000Z,
end_time: 2017-08-04T14:06:00.000Z,
booked: false
}
DB
also exposes a few utility methods to query and update the DB.meetings[]
array. See db.js
for the code.
The script ./app.js
creates, mounts and exports an ExpressJS app instance.
const express = require('express');
const bodyParser = require('body-parser');
// Create express instance
const app = express();
Set view engine and middleware parsers:
// view engine setup
app.set('view engine', 'ejs');
// parse data in request body
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
Mount routes:
// Mount the `./static` directory as a static file server
app.use(express.static(path.join(__dirname, 'static')));
// Mount the `./routes` module
app.use('/', require('./routes'));
Export the app
instance:
module.exports = app
app.js
also mounts a few other utility middleware. Take a look at the file for details.
The ./routes/
directory is built as a module to separate individual HTTP route segments in different files. routes/index.js
loads relevant routes from the same directory and mounts them on the exported route, which app.js
mounts at /
(root).
routes/index.js
:
const router = require('express').Router();
// Serve `home` view. This renders `views/home.ejs`
router.get('/', (req, res) => {
res.render('home');
});
// Load other routes and mount them
router.use('/setup', require('./setup_route'));
router.use('/dashboard', require('./dashboard_route'));
router.use('/meetings', require('./meetings_route'));
module.exports = router;
There are two dashboards in the demo, one for doctors and another for patients. They are almost identical with slight differences.
Patient dashboard shows meetings that the patient has booked and links to the page for booking available meeting slots. Doctor dashboard shows upcoming appointments that the doctor has created, including the ones which haven't been booked already by any patient. There is also a link to create new meeting slots. Both of the dashboards highlight current meeting with a link to join the meeting.
The ./routes/dashboard_route.js
contains the logic for querying DB
and rendering both dashboards.
Doctor dashboard renders the view at ./views/dashboard_doctor.ejs
. This is how the doctor dashboard (/dashboard/doctor/
) renders its view:
/**
* Doctor's dashboard
*/
router.get('/doctor', (req, res) => {
res.locals.user = { role: 'Doctor' };
res.render('dashboard_doctor', { meetings: DB.meetings_filter() })
});
The patient dashboard is served at /dashboard/patient
and its view is in ./views/dashboard_patient.ejs
. This is how patient dashboard (/dashboard/patient
) renders its view:
/**
* Patient's dashboard
*/
router.get('/patient', (req, res) => {
res.locals.user = { role: 'Patient' };
// Render view only with meetings that were booked
res.render('dashboard_patient', { meetings: DB.meetings_filter(true) });
});
./routes/meeting_route.js
handles route for creating, booking and joining meetings. Doctors can create meetings, patients can book available meetings and either can join a meeting that has been booked.
The route for joining meetings (/meetings/join/:meeting_id
) loads meeting details, fetches embed code and passes these to the view for rendering.
It replaces the room
parameter in the embed code's URL according to the meeting id. This is how the same OpenTok video embed is used for different meetings even at the same time:
// Here `req.embed_code` contains the orginal embed code obtained for an
// OpenTok video embed
const embed_code = req.embed_code.replace('DEFAULT_ROOM', `meeting${meeting.id}`);
So, this embed code:
<div id="otEmbedContainer" style="width:800px; height:640px"></div>
<script src="https://tokbox.com/embed/embed/ot-embed.js?embedId=<embedid>&room=DEFAULT_ROOM"></script>
becomes:
<!-- Where meeting id is 42 -->
<div id="otEmbedContainer" style="width:800px; height:640px"></div>
<script src="https://tokbox.com/embed/embed/ot-embed.js?embedId=<embedid>&room=meeting42"></script>
The view for joining meetings is at ./views/meeting.ejs
. This view simply adds the resulting embed code to the output if the meeting is not yet over.
This works for both <iframe>
and JavaScript versions of the embed code.
The script at ./bin/www
lauches the application. It loads the ExpressJS app
and launches a HTTP
server depending on the port configuration specified
Set up the app
and http
instances:
// Load dependencies, including `app.js` and the `models` module from project
// root
const app = require('../app');
const http = require('http');
// Get port from environment and store in Express.
const port = process.env.PORT || '3000';
app.set('port', port);
// Create HTTP server.
const server = http.createServer(app);
// Launch the server
server.listen(port);
Start the application server:
$ node ./bin/www
Note: This demo needs to be served over HTTPS. See INSTALL.md for details.