openvidu-call: Added recording
This commit is contained in:
parent
c8cd12c2b1
commit
0dc070bb38
1877
openvidu-call/openvidu-call-back/package-lock.json
generated
1877
openvidu-call/openvidu-call-back/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -4,14 +4,19 @@
|
||||
"url": "https://github.com/OpenVidu/openvidu-call/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "0.27.2",
|
||||
"btoa": "1.2.1",
|
||||
"cookie-parser": "1.4.6",
|
||||
"cookie-session": "^2.0.0",
|
||||
"dotenv": "16.0.0",
|
||||
"express": "4.17.3",
|
||||
"express": "4.18.1",
|
||||
"http-proxy-middleware": "2.0.6",
|
||||
"openvidu-node-client": "2.22.0"
|
||||
},
|
||||
"description": "OpenVidu Call Server",
|
||||
"devDependencies": {
|
||||
"@types/btoa": "1.2.3",
|
||||
"@types/cookie-parser": "1.4.3",
|
||||
"@types/express": "4.17.13",
|
||||
"@types/jest": "27.4.0",
|
||||
"@types/node": "17.0.10",
|
||||
|
||||
@ -1,29 +1,45 @@
|
||||
import * as express from 'express';
|
||||
import { SERVER_PORT, OPENVIDU_URL, OPENVIDU_SECRET, CALL_OPENVIDU_CERTTYPE } from './config';
|
||||
import {app as callController} from './controllers/CallController';
|
||||
import { SERVER_PORT, OPENVIDU_URL, OPENVIDU_SECRET, CALL_OPENVIDU_CERTTYPE, ADMIN_SECRET, RECORDING } from './config';
|
||||
import { app as sessionController } from './controllers/SessionController';
|
||||
import { app as adminController } from './controllers/AdminController';
|
||||
import { app as recordingController, proxyGETRecording } from './controllers/RecordingController';
|
||||
|
||||
import * as dotenv from 'dotenv';
|
||||
import * as cookieParser from 'cookie-parser';
|
||||
import * as cookieSession from 'cookie-session';
|
||||
|
||||
dotenv.config();
|
||||
const app = express();
|
||||
|
||||
|
||||
app.use(express.static(__dirname + '/public'));
|
||||
app.use(express.json());
|
||||
app.use(cookieParser());
|
||||
app.use(
|
||||
cookieSession({
|
||||
name: 'session',
|
||||
keys: [ADMIN_SECRET],
|
||||
maxAge: 24 * 60 * 60 * 1000 // 24 hours
|
||||
})
|
||||
);
|
||||
|
||||
app.use('/call', callController);
|
||||
app.use('/sessions', sessionController);
|
||||
app.use('/recordings', recordingController);
|
||||
app.use('/recordings/:recordingId', proxyGETRecording);
|
||||
app.use('/admin', adminController);
|
||||
|
||||
// Accept selfsigned certificates if CALL_OPENVIDU_CERTTYPE=selfsigned
|
||||
if (CALL_OPENVIDU_CERTTYPE === 'selfsigned') {
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
|
||||
}
|
||||
|
||||
app.listen(SERVER_PORT, () => {
|
||||
console.log("---------------------------------------------------------");
|
||||
console.log(" ")
|
||||
console.log(`OPENVIDU URL: ${OPENVIDU_URL}`);
|
||||
console.log(`OPENVIDU SECRET: ${OPENVIDU_SECRET}`);
|
||||
console.log(`CALL OPENVIDU CERTTYPE: ${CALL_OPENVIDU_CERTTYPE}`);
|
||||
console.log(`OpenVidu Call Server is listening on port ${SERVER_PORT}`);
|
||||
console.log(" ")
|
||||
console.log("---------------------------------------------------------");
|
||||
console.log('---------------------------------------------------------');
|
||||
console.log(' ');
|
||||
console.log(`OPENVIDU URL: ${OPENVIDU_URL}`);
|
||||
console.log(`OPENVIDU SECRET: ${OPENVIDU_SECRET}`);
|
||||
console.log(`CALL OPENVIDU CERTTYPE: ${CALL_OPENVIDU_CERTTYPE}`);
|
||||
console.log(`CALL RECORDING: ${RECORDING}`);
|
||||
console.log(`CALL ADMIN PASSWORD: ${ADMIN_SECRET}`);
|
||||
console.log(`OpenVidu Call Server is listening on port ${SERVER_PORT}`);
|
||||
console.log(' ');
|
||||
console.log('---------------------------------------------------------');
|
||||
});
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
export const SERVER_PORT = process.env.SERVER_PORT || 5000;
|
||||
export const OPENVIDU_URL = process.env.OPENVIDU_URL || 'https://localhost:4443';
|
||||
export const OPENVIDU_SECRET = process.env.OPENVIDU_SECRET || 'MY_SECRET';
|
||||
export const CALL_OPENVIDU_CERTTYPE = process.env.CALL_OPENVIDU_CERTTYPE;
|
||||
export const CALL_OPENVIDU_CERTTYPE = process.env.CALL_OPENVIDU_CERTTYPE || 'selfsigned';
|
||||
export const ADMIN_SECRET = process.env.ADMIN_SECRET || OPENVIDU_SECRET;
|
||||
export const RECORDING = process.env.RECORDING || 'ENABLED';
|
||||
|
||||
@ -0,0 +1,43 @@
|
||||
import * as express from 'express';
|
||||
import * as crypto from 'crypto';
|
||||
import { Request, Response } from 'express';
|
||||
|
||||
import { OpenViduService } from '../services/OpenViduService';
|
||||
import { ADMIN_SECRET } from '../config';
|
||||
|
||||
export const app = express.Router({
|
||||
strict: true
|
||||
});
|
||||
|
||||
const openviduService = OpenViduService.getInstance();
|
||||
|
||||
app.post('/login', async (req: Request, res: Response) => {
|
||||
const password = req.body.password;
|
||||
const isAdminTokenValid = openviduService.adminTokens.includes(req['session']?.token);
|
||||
const isAuthValid = password === ADMIN_SECRET || isAdminTokenValid;
|
||||
if (isAuthValid) {
|
||||
try {
|
||||
if (!req['session']?.token || !openviduService.adminTokens.includes(req['session']?.token)) {
|
||||
// Save session token
|
||||
const token = crypto.randomBytes(32).toString('hex');
|
||||
res.cookie(openviduService.ADMIN_TOKEN_NAME, token);
|
||||
req['session'] = { token };
|
||||
openviduService.adminTokens.push(token);
|
||||
}
|
||||
const recordings = await openviduService.listAllRecordings(true);
|
||||
console.log(`${recordings.length} recordings found`);
|
||||
res.status(200).send(JSON.stringify({ recordings }));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
res.status(500).send('Unexpected error getting recordings');
|
||||
}
|
||||
} else {
|
||||
res.status(403).send('Permissions denied');
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/logout', async (req: Request, res: Response) => {
|
||||
openviduService.adminTokens = openviduService.adminTokens.filter((token) => token !== req['session'].token);
|
||||
req['session'] = {};
|
||||
});
|
||||
|
||||
@ -1,38 +0,0 @@
|
||||
|
||||
import * as express from 'express';
|
||||
import { Request, Response } from 'express';
|
||||
import { Session } from 'openvidu-node-client';
|
||||
import { OpenViduService } from '../services/OpenViduService';
|
||||
export const app = express.Router({
|
||||
strict: true
|
||||
});
|
||||
|
||||
const openviduService = new OpenViduService();
|
||||
|
||||
app.post('/', async (req: Request, res: Response) => {
|
||||
let sessionId: string = req.body.sessionId;
|
||||
let nickname: string = req.body.nickname;
|
||||
let createdSession: Session = null;
|
||||
console.log('Session ID received', sessionId);
|
||||
try {
|
||||
createdSession = await openviduService.createSession(sessionId);
|
||||
} catch (error) {
|
||||
handleError(error, res);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const connection = await openviduService.createConnection(createdSession, nickname);
|
||||
res.status(200).send(JSON.stringify(connection.token));
|
||||
} catch (error) {
|
||||
handleError(error, res);
|
||||
}
|
||||
});
|
||||
|
||||
function handleError(error: any, res: Response){
|
||||
try {
|
||||
let statusCode = parseInt(error.message);
|
||||
res.status(parseInt(error.message)).send(`OpenVidu Server returned an error to OpenVidu Call Server: ${statusCode}`)
|
||||
} catch (error) {
|
||||
res.status(503).send('Cannot connect with OpenVidu Server');
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,172 @@
|
||||
import { CALL_OPENVIDU_CERTTYPE, OPENVIDU_URL, RECORDING } from '../config';
|
||||
|
||||
import * as express from 'express';
|
||||
import { Request, Response } from 'express';
|
||||
import { Recording } from 'openvidu-node-client';
|
||||
import { OpenViduService } from '../services/OpenViduService';
|
||||
import { createProxyMiddleware } from 'http-proxy-middleware';
|
||||
export const app = express.Router({
|
||||
strict: true
|
||||
});
|
||||
|
||||
const openviduService = OpenViduService.getInstance();
|
||||
|
||||
app.get('/', async (req: Request, res: Response) => {
|
||||
try {
|
||||
const IS_RECORDING_ENABLED = RECORDING.toUpperCase() === 'ENABLED';
|
||||
const sessionId = openviduService.getSessionIdFromCookie(req.cookies);
|
||||
const isAdminDashboard = openviduService.adminTokens.includes(req['session'].token);
|
||||
let recordings = [];
|
||||
if ((!!sessionId && IS_RECORDING_ENABLED && openviduService.isValidToken(sessionId, req.cookies)) || isAdminDashboard) {
|
||||
if (isAdminDashboard) {
|
||||
recordings = await openviduService.listAllRecordings(true);
|
||||
} else {
|
||||
const date = openviduService.getDateFromCookie(req.cookies);
|
||||
recordings = await openviduService.listRecordingsBySessionIdAndDate(sessionId, date);
|
||||
}
|
||||
console.log('a', recordings)
|
||||
res.status(200).send(JSON.stringify(recordings));
|
||||
} else {
|
||||
const message = IS_RECORDING_ENABLED ? 'Permissions denied to drive recording' : 'Recording is disabled';
|
||||
res.status(403).send(JSON.stringify({ message }));
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
const code = Number(error?.message);
|
||||
let message = 'Unexpected error getting all recordings';
|
||||
if (code === 404) {
|
||||
message = 'No recording exist for the session';
|
||||
}
|
||||
return res.status(Number(code) || 500).send(JSON.stringify({ message }));
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/start', async (req: Request, res: Response) => {
|
||||
try {
|
||||
let sessionId: string = req.body.sessionId;
|
||||
if (openviduService.isValidToken(sessionId, req.cookies)) {
|
||||
let startingRecording: Recording = null;
|
||||
console.log(`Starting recording in ${sessionId}`);
|
||||
startingRecording = await openviduService.startRecording(sessionId);
|
||||
openviduService.recordingMap.get(sessionId).recordingId = startingRecording.id;
|
||||
res.status(200).send(JSON.stringify(startingRecording));
|
||||
} else {
|
||||
console.log(`Permissions denied for starting recording in session ${sessionId}`);
|
||||
res.status(403).send(JSON.stringify({ message: 'Permissions denied to drive recording' }));
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
const code = Number(error?.message);
|
||||
let message = `Unexpected error starting recording`;
|
||||
if (code === 409) {
|
||||
message = 'The session is already being recorded.';
|
||||
} else if (code === 501) {
|
||||
message = 'OpenVidu Server recording module is disabled';
|
||||
} else if (code === 406) {
|
||||
message = 'The session has no connected participants';
|
||||
}
|
||||
return res.status(code || 500).send(JSON.stringify({ message }));
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/stop', async (req: Request, res: Response) => {
|
||||
try {
|
||||
let sessionId: string = req.body.sessionId;
|
||||
if (openviduService.isValidToken(sessionId, req.cookies)) {
|
||||
const recordingId = openviduService.recordingMap.get(sessionId)?.recordingId;
|
||||
|
||||
if (!!recordingId) {
|
||||
console.log(`Stopping recording in ${sessionId}`);
|
||||
await openviduService.stopRecording(recordingId);
|
||||
const date = openviduService.getDateFromCookie(req.cookies);
|
||||
const recordingList = await openviduService.listRecordingsBySessionIdAndDate(sessionId, date);
|
||||
openviduService.recordingMap.get(sessionId).recordingId = '';
|
||||
res.status(200).send(JSON.stringify(recordingList));
|
||||
} else {
|
||||
res.status(404).send(JSON.stringify({ message: 'Session was not being recorded' }));
|
||||
}
|
||||
} else {
|
||||
res.status(403).send(JSON.stringify({ message: 'Permissions denied to drive recording' }));
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
const code = Number(error?.message);
|
||||
let message = `Unexpected error stopping recording`;
|
||||
if (code === 501) {
|
||||
message = 'OpenVidu Server recording module is disabled';
|
||||
} else if (code === 406) {
|
||||
message = 'Recording has STARTING status. Wait until STARTED status before stopping the recording';
|
||||
}
|
||||
return res.status(code || 500).send(JSON.stringify({ message }));
|
||||
}
|
||||
});
|
||||
|
||||
app.delete('/delete/:recordingId', async (req: Request, res: Response) => {
|
||||
try {
|
||||
const sessionId = openviduService.getSessionIdFromCookie(req.cookies);
|
||||
const isAdminDashboard = openviduService.adminTokens.includes(req['session'].token);
|
||||
let recordings = [];
|
||||
if ((!!sessionId && openviduService.isValidToken(sessionId, req.cookies)) || isAdminDashboard) {
|
||||
console.log('DELETE RECORDING');
|
||||
const recordingId: string = req.params.recordingId;
|
||||
if (!recordingId) {
|
||||
return res.status(400).send('Missing recording id parameter.');
|
||||
}
|
||||
await openviduService.deleteRecording(recordingId);
|
||||
if (isAdminDashboard) {
|
||||
recordings = await openviduService.listAllRecordings(true);
|
||||
} else {
|
||||
const date = openviduService.getDateFromCookie(req.cookies);
|
||||
recordings = await openviduService.listRecordingsBySessionIdAndDate(sessionId, date);
|
||||
}
|
||||
res.status(200).send(JSON.stringify(recordings));
|
||||
} else {
|
||||
res.status(403).send(JSON.stringify({ message: 'Permissions denied to drive recording' }));
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
const code = Number(error?.message);
|
||||
let message = `Unexpected error deleting the recording`;
|
||||
if (code === 409) {
|
||||
message = 'The recording has STARTED status. Stop it before deletion.';
|
||||
} else if (code === 501) {
|
||||
message = 'OpenVidu Server recording module is disabled';
|
||||
} else if (code === 409) {
|
||||
message = 'No recording exists for the session';
|
||||
}
|
||||
return res.status(code).send(JSON.stringify({ message }));
|
||||
}
|
||||
});
|
||||
|
||||
export const proxyGETRecording = createProxyMiddleware({
|
||||
target: `${OPENVIDU_URL}/openvidu/recordings/`,
|
||||
secure: CALL_OPENVIDU_CERTTYPE !== 'selfsigned',
|
||||
pathRewrite: (path, req) => {
|
||||
return `${req.params.recordingId}/${req.params.recordingId}.mp4`;
|
||||
},
|
||||
onProxyReq: (proxyReq, req: Request, res: Response) => {
|
||||
const isAdminDashboard = openviduService.adminTokens.includes(req['session'].token);
|
||||
const sessionId = openviduService.getSessionIdFromCookie(req.cookies);
|
||||
if ((!!sessionId && openviduService.isValidToken(sessionId, req.cookies)) || isAdminDashboard) {
|
||||
const recordingId: string = req.params.recordingId;
|
||||
if (!recordingId) {
|
||||
return res.status(400).send(JSON.stringify({ message: 'Missing recording id parameter.' }));
|
||||
} else {
|
||||
proxyReq.setHeader('Authorization', openviduService.getBasicAuth());
|
||||
proxyReq.setHeader('Range', 'bytes=0-');
|
||||
}
|
||||
} else {
|
||||
return res.status(403).send(JSON.stringify({ message: 'Permissions denied to drive recording' }));
|
||||
}
|
||||
},
|
||||
onError: (error, req: Request, res: Response) => {
|
||||
console.log(error);
|
||||
const code = Number(error?.message);
|
||||
let message = 'Unexpected error downloading the recording';
|
||||
if (code === 404) {
|
||||
message = 'No recording exist for the session';
|
||||
}
|
||||
res.status(Number(code) || 500).send(JSON.stringify({ message }));
|
||||
return res.end();
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,68 @@
|
||||
import * as express from 'express';
|
||||
import * as crypto from 'crypto';
|
||||
import { Request, Response } from 'express';
|
||||
import { OpenViduRole, Session } from 'openvidu-node-client';
|
||||
import { OpenViduService } from '../services/OpenViduService';
|
||||
import { RECORDING } from '../config';
|
||||
|
||||
export const app = express.Router({
|
||||
strict: true
|
||||
});
|
||||
|
||||
const openviduService = OpenViduService.getInstance();
|
||||
|
||||
app.post('/', async (req: Request, res: Response) => {
|
||||
try {
|
||||
console.log('Session ID received', req.body.sessionId);
|
||||
|
||||
let sessionId: string = req.body.sessionId;
|
||||
let nickname: string = req.body.nickname;
|
||||
let date = null;
|
||||
let sessionCreated: Session = await openviduService.createSession(sessionId);
|
||||
const RECORDING_TOKEN_NAME = openviduService.RECORDING_TOKEN_NAME;
|
||||
const IS_RECORDING_ENABLED = RECORDING.toUpperCase() === 'ENABLED';
|
||||
const hasValidToken = openviduService.isValidToken(sessionId, req.cookies);
|
||||
const isSessionCreator = hasValidToken || sessionCreated.activeConnections.length === 0;
|
||||
const role: OpenViduRole = isSessionCreator && IS_RECORDING_ENABLED ? OpenViduRole.MODERATOR : OpenViduRole.PUBLISHER;
|
||||
const response = {cameraToken : '', screenToken: '', recording: IS_RECORDING_ENABLED};
|
||||
const cameraConnection = await openviduService.createConnection(sessionCreated, nickname, role);
|
||||
const screenConnection = await openviduService.createConnection(sessionCreated, nickname, role);
|
||||
response.cameraToken = cameraConnection.token;
|
||||
response.screenToken = screenConnection.token;
|
||||
|
||||
if (IS_RECORDING_ENABLED && isSessionCreator && !hasValidToken) {
|
||||
/**
|
||||
* ! *********** WARN *********** !
|
||||
*
|
||||
* To identify who is able to manage session recording, the code sends a cookie with a token to the session creator.
|
||||
* The relation between cookies and sessions are stored in backend memory.
|
||||
*
|
||||
* This authentication & autorization system is pretty basic and it is not for production.
|
||||
* We highly recommend IMPLEMENT YOUR OWN USER MANAGEMENT with persistence for a properly and secure recording feature.
|
||||
*
|
||||
* ! *********** WARN *********** !
|
||||
**/
|
||||
const uuid = crypto.randomBytes(32).toString('hex');
|
||||
date = Date.now();
|
||||
const recordingToken = `${response.cameraToken}&${RECORDING_TOKEN_NAME}=${uuid}&createdAt=${date}`;
|
||||
res.cookie(RECORDING_TOKEN_NAME, recordingToken);
|
||||
openviduService.recordingMap.set(sessionId, { token: recordingToken, recordingId: '' });
|
||||
}
|
||||
|
||||
if(IS_RECORDING_ENABLED){
|
||||
date = date || openviduService.getDateFromCookie(req.cookies);
|
||||
response['recordings'] = await openviduService.listRecordingsBySessionIdAndDate(sessionId, date);
|
||||
}
|
||||
|
||||
res.status(200).send(JSON.stringify(response));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
let message = 'Cannot connect with OpenVidu Server';
|
||||
if(error.message === 500){
|
||||
message = 'Unexpected error when creating the Connection object.'
|
||||
} else if (error.message === 404){
|
||||
message = 'No session exists';
|
||||
}
|
||||
res.status(error?.message || 503).send({ message });
|
||||
}
|
||||
});
|
||||
@ -1,27 +1,120 @@
|
||||
import { Connection, ConnectionProperties, OpenVidu, Session, SessionProperties } from "openvidu-node-client";
|
||||
import { Connection, ConnectionProperties, OpenVidu, OpenViduRole, Recording, Session, SessionProperties } from 'openvidu-node-client';
|
||||
import { OPENVIDU_URL, OPENVIDU_SECRET } from '../config';
|
||||
import axios from 'axios';
|
||||
|
||||
export class OpenViduService {
|
||||
RECORDING_TOKEN_NAME = 'ovCallRecordingToken';
|
||||
ADMIN_TOKEN_NAME = 'ovCallAdminToken';
|
||||
recordingMap: Map<string, { token: string; recordingId: string }> = new Map<string, { token: string; recordingId: string }>();
|
||||
adminTokens: string[] = [];
|
||||
protected static instance: OpenViduService;
|
||||
private openvidu: OpenVidu;
|
||||
|
||||
private openvidu: OpenVidu;
|
||||
|
||||
constructor(){
|
||||
this.openvidu = new OpenVidu(OPENVIDU_URL, OPENVIDU_SECRET);
|
||||
}
|
||||
|
||||
public async createSession(sessionId: string): Promise<Session> {
|
||||
console.log("Creating session: ", sessionId);
|
||||
let sessionProperties: SessionProperties = {customSessionId: sessionId};
|
||||
return await this.openvidu.createSession(sessionProperties);
|
||||
private constructor() {
|
||||
this.openvidu = new OpenVidu(OPENVIDU_URL, OPENVIDU_SECRET);
|
||||
}
|
||||
|
||||
public async createConnection(session: Session, nickname: string): Promise<Connection> {
|
||||
console.log(`Requesting token for session ${session.sessionId}`);
|
||||
let connectionProperties: ConnectionProperties = {}
|
||||
if(!!nickname) {
|
||||
connectionProperties.data = JSON.stringify({ openviduCustomConnectionId: nickname });
|
||||
}
|
||||
console.log('Connection Properties:', connectionProperties);
|
||||
return await session.createConnection(connectionProperties);
|
||||
}
|
||||
static getInstance() {
|
||||
if (!OpenViduService.instance) {
|
||||
OpenViduService.instance = new OpenViduService();
|
||||
}
|
||||
return OpenViduService.instance;
|
||||
}
|
||||
|
||||
getBasicAuth(): string {
|
||||
return this.openvidu.basicAuth;
|
||||
}
|
||||
|
||||
getDateFromCookie(cookies: any): number {
|
||||
const cookieToken = cookies[this.RECORDING_TOKEN_NAME];
|
||||
if (!!cookieToken) {
|
||||
const cookieTokenUrl = new URL(cookieToken);
|
||||
const date = cookieTokenUrl?.searchParams.get('createdAt');
|
||||
return Number(date);
|
||||
} else {
|
||||
return Date.now();
|
||||
}
|
||||
}
|
||||
|
||||
getSessionIdFromCookie(cookies: any): string {
|
||||
try {
|
||||
const cookieTokenUrl = new URL(cookies[this.RECORDING_TOKEN_NAME]);
|
||||
return cookieTokenUrl?.searchParams.get('sessionId');
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
isValidToken(sessionId: string, cookies: any): boolean {
|
||||
try {
|
||||
const storedTokenUrl = new URL(this.recordingMap.get(sessionId)?.token);
|
||||
const cookieTokenUrl = new URL(cookies[this.RECORDING_TOKEN_NAME]);
|
||||
if (!!cookieTokenUrl && !!storedTokenUrl) {
|
||||
const cookieSessionId = cookieTokenUrl.searchParams.get('sessionId');
|
||||
const cookieToken = cookieTokenUrl.searchParams.get(this.RECORDING_TOKEN_NAME);
|
||||
const cookieDate = cookieTokenUrl.searchParams.get('createdAt');
|
||||
|
||||
const storedToken = storedTokenUrl.searchParams.get(this.RECORDING_TOKEN_NAME);
|
||||
const storedDate = storedTokenUrl.searchParams.get('createdAt');
|
||||
|
||||
return sessionId === cookieSessionId && cookieToken === storedToken && cookieDate === storedDate;
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async createSession(sessionId: string): Promise<Session> {
|
||||
console.log('Creating session: ', sessionId);
|
||||
let sessionProperties: SessionProperties = { customSessionId: sessionId };
|
||||
const session = await this.openvidu.createSession(sessionProperties);
|
||||
await session.fetch();
|
||||
return session;
|
||||
}
|
||||
|
||||
public createConnection(session: Session, nickname: string, role: OpenViduRole): Promise<Connection> {
|
||||
console.log(`Requesting token for session ${session.sessionId}`);
|
||||
let connectionProperties: ConnectionProperties = { role };
|
||||
if (!!nickname) {
|
||||
connectionProperties.data = JSON.stringify({ openviduCustomConnectionId: nickname });
|
||||
}
|
||||
console.log('Connection Properties:', connectionProperties);
|
||||
return session.createConnection(connectionProperties);
|
||||
}
|
||||
|
||||
public async startRecording(sessionId: string): Promise<Recording> {
|
||||
return this.openvidu.startRecording(sessionId);
|
||||
}
|
||||
|
||||
public stopRecording(recordingId: string): Promise<Recording> {
|
||||
return this.openvidu.stopRecording(recordingId);
|
||||
}
|
||||
|
||||
public deleteRecording(recordingId: string): Promise<Error> {
|
||||
return this.openvidu.deleteRecording(recordingId);
|
||||
}
|
||||
public getRecording(recordingId: string): Promise<Recording> {
|
||||
return this.openvidu.getRecording(recordingId);
|
||||
}
|
||||
|
||||
public async listAllRecordings(withB64Thumbnail: boolean = false): Promise<Recording[]> {
|
||||
let recordings = await this.openvidu.listRecordings();
|
||||
if (withB64Thumbnail) {
|
||||
for (const rec of recordings) {
|
||||
let thumbnailUrl = `${rec.url.substring(0, rec.url.lastIndexOf('/'))}/${rec.id}.jpg`;
|
||||
const headers = { Authorization: this.getBasicAuth() };
|
||||
let image = await axios.get(thumbnailUrl, { headers, responseType: 'arraybuffer' });
|
||||
rec['thumbnailB64'] = `data:${image.headers['content-type']};base64,${Buffer.from(image.data).toString('base64')}`;
|
||||
}
|
||||
}
|
||||
return recordings;
|
||||
}
|
||||
|
||||
public async listRecordingsBySessionIdAndDate(sessionId: string, date: number) {
|
||||
const recordingList: Recording[] = await this.listAllRecordings();
|
||||
return recordingList.filter((recording) => recording.sessionId === sessionId && date <= recording.createdAt);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,2 +1,15 @@
|
||||
<ov-videoconference (onJoinButtonClicked)="onJoinButtonClicked()" (onToolbarLeaveButtonClicked)="onLeaveButtonClicked()" [tokens]="tokens">
|
||||
<ov-videoconference
|
||||
[tokens]="tokens"
|
||||
[recordingActivityRecordingsList]="recordingList"
|
||||
[recordingActivityRecordingError]="recordingError"
|
||||
(onToolbarLeaveButtonClicked)="onLeaveButtonClicked()"
|
||||
(onToolbarStartRecordingClicked)="onStartRecordingClicked()"
|
||||
(onActivitiesPanelStartRecordingClicked)="onStartRecordingClicked()"
|
||||
(onToolbarStopRecordingClicked)="onStopRecordingClicked()"
|
||||
(onActivitiesPanelStopRecordingClicked)="onStopRecordingClicked()"
|
||||
(onActivitiesPanelDownloadRecordingClicked)="onDownloadRecordingClicked($event)"
|
||||
(onActivitiesPanelDeleteRecordingClicked)="onDeleteRecordingClicked($event)"
|
||||
(onActivitiesPanelPlayRecordingClicked)="onPlayRecordingClicked($event)"
|
||||
(onRefreshRecordingsClicked)="onRefreshRecordingsClicked()"
|
||||
>
|
||||
</ov-videoconference>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute, Params, Router } from '@angular/router';
|
||||
import { ParticipantService } from 'openvidu-angular';
|
||||
import { ParticipantService, RecordingInfo, RecordingService, TokenModel } from 'openvidu-angular';
|
||||
|
||||
import { RestService } from '../../services/rest.service';
|
||||
|
||||
@ -11,14 +11,18 @@ import { RestService } from '../../services/rest.service';
|
||||
})
|
||||
export class CallComponent implements OnInit {
|
||||
sessionId = '';
|
||||
tokens: { webcam: string; screen: string };
|
||||
tokens: TokenModel;
|
||||
joinSessionClicked: boolean = false;
|
||||
closeClicked: boolean = false;
|
||||
isSessionAlive: boolean = false;
|
||||
recordingList: RecordingInfo[] = [];
|
||||
|
||||
recordingError: any;
|
||||
|
||||
constructor(
|
||||
private restService: RestService,
|
||||
private participantService: ParticipantService,
|
||||
private recordingService: RecordingService,
|
||||
private router: Router,
|
||||
private route: ActivatedRoute
|
||||
) {}
|
||||
@ -37,16 +41,56 @@ export class CallComponent implements OnInit {
|
||||
nickname = this.participantService.getLocalParticipant().getNickname();
|
||||
}
|
||||
|
||||
const response = await this.restService.getTokens(this.sessionId, nickname);
|
||||
this.recordingList = response.recordings;
|
||||
this.tokens = {
|
||||
webcam: await this.restService.getToken(this.sessionId, nickname),
|
||||
screen: await this.restService.getToken(this.sessionId, nickname)
|
||||
webcam: response.cameraToken,
|
||||
screen: response.screenToken
|
||||
};
|
||||
}
|
||||
|
||||
async onJoinButtonClicked() {}
|
||||
onLeaveButtonClicked() {
|
||||
this.isSessionAlive = false;
|
||||
this.closeClicked = true;
|
||||
this.router.navigate([`/`]);
|
||||
}
|
||||
async onStartRecordingClicked() {
|
||||
try {
|
||||
await this.restService.startRecording(this.sessionId);
|
||||
} catch (error) {
|
||||
this.recordingError = error;
|
||||
}
|
||||
}
|
||||
|
||||
async onStopRecordingClicked() {
|
||||
try {
|
||||
this.recordingList = await this.restService.stopRecording(this.sessionId);
|
||||
} catch (error) {
|
||||
this.recordingError = error;
|
||||
}
|
||||
}
|
||||
|
||||
async onDeleteRecordingClicked(recordingId: string) {
|
||||
try {
|
||||
this.recordingList = await this.restService.deleteRecording(recordingId);
|
||||
} catch (error) {
|
||||
this.recordingError = error;
|
||||
}
|
||||
}
|
||||
async onDownloadRecordingClicked(recordingId: string) {
|
||||
try {
|
||||
const file = await this.restService.downloadRecording(recordingId);
|
||||
this.recordingService.downloadRecording(recordingId, file);
|
||||
} catch (error) {
|
||||
this.recordingError = error;
|
||||
}
|
||||
}
|
||||
|
||||
async onPlayRecordingClicked(recordingId: string) {
|
||||
try {
|
||||
const recording: Blob = await this.restService.downloadRecording(recordingId);
|
||||
this.recordingService.playRecording(recording);
|
||||
} catch (error) {
|
||||
this.recordingError = error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||
import { catchError, lastValueFrom } from 'rxjs';
|
||||
import { catchError, lastValueFrom, Observable } from 'rxjs';
|
||||
import { RecordingInfo } from 'openvidu-angular';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
@ -11,9 +12,42 @@ export class RestService {
|
||||
constructor(private http: HttpClient) {
|
||||
this.baseHref = '/' + (!!window.location.pathname.split('/')[1] ? window.location.pathname.split('/')[1] + '/' : '');
|
||||
}
|
||||
async getToken(sessionId: string, nickname?: string): Promise<string> {
|
||||
async getTokens(
|
||||
sessionId: string,
|
||||
nickname?: string
|
||||
): Promise<{ cameraToken: string; screenToken: string; recordings?: RecordingInfo[] }> {
|
||||
return this.postRequest('sessions', { sessionId, nickname });
|
||||
}
|
||||
login(password: string): Promise<any[]> {
|
||||
return this.postRequest('admin/login', { password });
|
||||
}
|
||||
logout(): Promise<void> {
|
||||
return this.postRequest('admin/logout', {});
|
||||
}
|
||||
|
||||
getRecordings() {
|
||||
return this.getRequest(`recordings/`);
|
||||
}
|
||||
|
||||
startRecording(sessionId: string) {
|
||||
return this.postRequest('recordings/start', { sessionId });
|
||||
}
|
||||
|
||||
stopRecording(sessionId: string) {
|
||||
return this.postRequest('recordings/stop', { sessionId });
|
||||
}
|
||||
|
||||
downloadRecording(recordingId: string): Promise<Blob> {
|
||||
return this.getRequest(`recordings/${recordingId}`, 'blob');
|
||||
}
|
||||
|
||||
deleteRecording(recordingId: string): Promise<RecordingInfo[]> {
|
||||
return this.deleteRequest(`recordings/delete/${recordingId}`);
|
||||
}
|
||||
|
||||
private postRequest(path: string, body: any): any {
|
||||
try {
|
||||
return lastValueFrom(this.http.post<any>(this.baseHref + 'call', { sessionId, nickname }));
|
||||
return lastValueFrom(this.http.post<any>(this.baseHref + path, body));
|
||||
} catch (error) {
|
||||
if (error.status === 404) {
|
||||
throw { status: error.status, message: 'Cannot connect with backend. ' + error.url + ' not found' };
|
||||
@ -21,4 +55,31 @@ export class RestService {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private getRequest(path: string, responseType?: string): any {
|
||||
try {
|
||||
const options = {};
|
||||
if (responseType) {
|
||||
options['responseType'] = responseType;
|
||||
}
|
||||
return lastValueFrom(this.http.get(`${this.baseHref}${path}`, options));
|
||||
} catch (error) {
|
||||
if (error.status === 404) {
|
||||
throw { status: error.status, message: 'Cannot connect with backend. ' + error.url + ' not found' };
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private deleteRequest(path: string) {
|
||||
try {
|
||||
return lastValueFrom(this.http.delete<any>(`${this.baseHref}${path}`));
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
if (error.status === 404) {
|
||||
throw { status: error.status, message: 'Cannot connect with backend. ' + error.url + ' not found' };
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,7 +5,6 @@
|
||||
<title>OpenviduCall</title>
|
||||
<base href="/" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet" />
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user