201 lines
5.5 KiB
JavaScript
201 lines
5.5 KiB
JavaScript
/* CONFIGURATION */
|
|
require('dotenv').config(
|
|
!!process.env.CONFIG ? { path: process.env.CONFIG } : {}
|
|
);
|
|
// For demo purposes we ignore self-signed certificate
|
|
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
|
|
|
|
// Node imports
|
|
const express = require('express');
|
|
const fs = require('fs');
|
|
var path = require('path');
|
|
const https = require('https');
|
|
const bodyParser = require('body-parser');
|
|
const AccessToken = require('livekit-server-sdk').AccessToken;
|
|
const EgressClient = require('livekit-server-sdk').EgressClient;
|
|
const cors = require('cors');
|
|
const app = express();
|
|
|
|
// Environment variable: PORT where the node server is listening
|
|
const SERVER_PORT = process.env.SERVER_PORT || 5000;
|
|
// Environment variable: api key shared with our LiveKit deployment
|
|
const LIVEKIT_API_KEY = process.env.LIVEKIT_API_KEY || 'devkey';
|
|
// Environment variable: api secret shared with our LiveKit deployment
|
|
const LIVEKIT_API_SECRET = process.env.LIVEKIT_API_SECRET || 'secret';
|
|
// Environment variable: url of our LiveKit deployment
|
|
const LIVEKIT_URL = process.env.LIVEKIT_URL || 'ws://localhost:7880';
|
|
// Environment variable: path where the recordings will be stored
|
|
const RECORDINGS_PATH = process.env.RECORDINGS_PATH || '/recordings';
|
|
|
|
// Listen (start app with node server.js)
|
|
const options = {
|
|
key: fs.readFileSync('openvidukey.pem'),
|
|
cert: fs.readFileSync('openviducert.pem'),
|
|
};
|
|
|
|
const livekitUrlHostname = LIVEKIT_URL.replace(/^ws:/, 'http:').replace(
|
|
/^wss:/,
|
|
'https:'
|
|
);
|
|
const egressClient = new EgressClient(
|
|
livekitUrlHostname,
|
|
LIVEKIT_API_KEY,
|
|
LIVEKIT_API_SECRET
|
|
);
|
|
|
|
// Enable CORS support
|
|
app.use(
|
|
cors({
|
|
origin: '*',
|
|
})
|
|
);
|
|
|
|
// Set the static files location
|
|
app.use(express.static(__dirname + '/public'));
|
|
// Parse application/x-www-form-urlencoded
|
|
app.use(
|
|
bodyParser.urlencoded({
|
|
extended: 'true',
|
|
})
|
|
);
|
|
// Parse application/json
|
|
app.use(bodyParser.json());
|
|
|
|
// Parse application/vnd.api+json as json
|
|
app.use(
|
|
bodyParser.json({
|
|
type: 'application/vnd.api+json',
|
|
})
|
|
);
|
|
|
|
https.createServer(options, app).listen(SERVER_PORT, () => {
|
|
console.log(`App listening on port ${SERVER_PORT}`);
|
|
console.log(`LIVEKIT API KEY: ${LIVEKIT_API_KEY}`);
|
|
console.log(`LIVEKIT API SECRET: ${LIVEKIT_API_SECRET}`);
|
|
console.log(`LIVEKIT URL: ${LIVEKIT_URL}`);
|
|
console.log();
|
|
console.log('Access the app at https://localhost:' + SERVER_PORT);
|
|
});
|
|
|
|
/* Session API */
|
|
|
|
app.post('/token', (req, res) => {
|
|
const { roomName, participantName } = req.body;
|
|
|
|
console.log(
|
|
`Getting a token for room '${roomName}' and participant '${participantName}'`
|
|
);
|
|
|
|
if (!roomName || !participantName) {
|
|
res
|
|
.status(400)
|
|
.json({ message: 'roomName and participantName are required' });
|
|
return;
|
|
}
|
|
|
|
const at = new AccessToken(LIVEKIT_API_KEY, LIVEKIT_API_SECRET, {
|
|
identity: participantName,
|
|
// add metadata to the token, which will be available in the participant's metadata
|
|
metadata: JSON.stringify({ livekitUrl: LIVEKIT_URL }),
|
|
});
|
|
at.addGrant({
|
|
roomJoin: true,
|
|
room: roomName,
|
|
});
|
|
res.status(200).json({ token: at.toJwt() });
|
|
});
|
|
|
|
/* Recording API */
|
|
|
|
// Start recording
|
|
app.post('/recordings/start', async function (req, res) {
|
|
const {
|
|
roomName,
|
|
outputMode,
|
|
videoOnly,
|
|
audioOnly,
|
|
audioTrackId,
|
|
videoTrackId,
|
|
} = req.body;
|
|
const output = {
|
|
fileType: 0, // file type chosen based on codecs
|
|
filepath: `/recordings/${roomName}-${new Date().getTime()}`,
|
|
disableManifest: true,
|
|
};
|
|
console.log('Starting recording', roomName);
|
|
try {
|
|
let egressInfo;
|
|
if (outputMode === 'COMPOSED') {
|
|
console.log('Starting COMPOSED recording', roomName);
|
|
egressInfo = await egressClient.startRoomCompositeEgress(
|
|
roomName,
|
|
output,
|
|
{
|
|
layout: 'grid',
|
|
audioOnly,
|
|
videoOnly,
|
|
}
|
|
);
|
|
} else if (outputMode === 'INDIVIDUAL') {
|
|
console.log('Starting INDIVIDUAL recording', roomName);
|
|
egressInfo = await egressClient.startTrackCompositeEgress(
|
|
roomName,
|
|
output,
|
|
{
|
|
audioTrackId,
|
|
videoTrackId,
|
|
}
|
|
);
|
|
} else {
|
|
res.status(400).json({ message: 'outputMode is required' });
|
|
return;
|
|
}
|
|
res.status(200).json({ message: 'recording started', info: egressInfo });
|
|
} catch (error) {
|
|
console.log('Error starting recording', error);
|
|
res.status(200).json({ message: 'error starting recording' });
|
|
}
|
|
});
|
|
|
|
// Stop recording
|
|
app.post('/recordings/stop', async function (req, res) {
|
|
const recordingId = req.body.recordingId;
|
|
try {
|
|
if (!recordingId) {
|
|
res.status(400).json({ message: 'recordingId is required' });
|
|
return;
|
|
}
|
|
|
|
console.log(`Stopping recording ${recordingId}`);
|
|
const egressInfo = await egressClient.stopEgress(recordingId);
|
|
res.status(200).json({ message: 'recording stopped', info: egressInfo });
|
|
} catch (error) {
|
|
console.log('Error stopping recording', error);
|
|
res.status(200).json({ message: 'error stopping recording' });
|
|
}
|
|
});
|
|
|
|
// List all recordings
|
|
app.get('/recordings/list', function (req, res) {
|
|
const recordings = [];
|
|
fs.readdirSync(RECORDINGS_PATH, { recursive: true }).forEach((value) => {
|
|
// copy file to public folder for development purposes
|
|
fs.copyFileSync(`${RECORDINGS_PATH}/${value}`, `public/${value}`);
|
|
const newRec = { name: value, path: `/${value}` };
|
|
recordings.push(newRec);
|
|
});
|
|
console.log(recordings);
|
|
res.status(200).json({ recordings });
|
|
});
|
|
|
|
// Delete all recordings
|
|
app.delete('/recordings', function (req, res) {
|
|
fs.readdirSync(RECORDINGS_PATH, { recursive: true }).forEach((value) => {
|
|
fs.unlinkSync(`${RECORDINGS_PATH}/${value}`);
|
|
if (fs.existsSync(`public/${value}`)) {
|
|
fs.unlinkSync(`public/${value}`);
|
|
}
|
|
});
|
|
res.status(200).json({ message: 'All recordings deleted' });
|
|
});
|