2023-11-20 13:48:35 +01:00

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' });
});