Add OpenVidu Meet demo app

This commit is contained in:
juancarmore 2025-10-20 13:24:46 +02:00
parent 054bd1594e
commit bad2a735b6
11 changed files with 1323 additions and 0 deletions

3
meet-demo/.env Normal file
View File

@ -0,0 +1,3 @@
SERVER_PORT=6080
OV_MEET_SERVER_URL=http://localhost:9080
OV_MEET_API_KEY=meet-api-key

24
meet-demo/Dockerfile Normal file
View File

@ -0,0 +1,24 @@
FROM node:22-alpine
WORKDIR /app
# Copy package files and install dependencies
COPY package*.json ./
RUN npm ci --only=production
# Copy application files
COPY src ./src
COPY static ./static
# Use non-root user
USER node
# Expose port
EXPOSE 6080
# Set environment variables
ENV NODE_ENV=production \
SERVER_PORT=6080
# Start the application
CMD ["node", "src/index.js"]

65
meet-demo/README.md Normal file
View File

@ -0,0 +1,65 @@
# OpenVidu Meet Demo
## Prerequisites
- [Node](https://nodejs.org/en/download) (for local development)
- [Docker](https://docs.docker.com/get-docker/) (for containerized deployment)
## Run Locally
> [!NOTE]
> Before running the application, you must also run [OpenVidu Local Deployment](https://github.com/OpenVidu/openvidu-local-deployment).
1. Download repository
```bash
git clone https://github.com/OpenVidu/openvidu-meet-tutorials.git
cd openvidu-meet-tutorials/meet-demo
```
2. Install dependencies
```bash
npm install
```
3. Run the application
```bash
npm start
```
4. Access the application at `http://localhost:6080`
## Run with Docker
### Using Docker CLI
1. Build the Docker image
```bash
docker build -t openvidu/openvidu-meet-demo .
```
2. Run the container
```bash
docker run -d \
--name meet-demo \
-p 6080:6080 \
-e OV_MEET_SERVER_URL=https://meet.openvidu.io \
-e OV_MEET_API_KEY=meet-api-key \
openvidu/openvidu-meet-demo
```
3. Access the application at `http://localhost:6080`
## Configuration
The application can be configured using environment variables:
| Variable | Description | Default |
| -------------------- | ---------------------------------------- | ----------------------- |
| `SERVER_PORT` | Port where the server will listen | `6080` |
| `OV_MEET_SERVER_URL` | URL of the OpenVidu Meet server | `http://localhost:9080` |
| `OV_MEET_API_KEY` | API key for OpenVidu Meet authentication | `meet-api-key` |

877
meet-demo/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

14
meet-demo/package.json Normal file
View File

@ -0,0 +1,14 @@
{
"name": "meet-demo",
"version": "1.0.0",
"main": "src/index.js",
"type": "module",
"scripts": {
"start": "node src/index.js"
},
"dependencies": {
"cors": "2.8.5",
"dotenv": "17.2.3",
"express": "5.1.0"
}
}

95
meet-demo/src/index.js Normal file
View File

@ -0,0 +1,95 @@
import bodyParser from 'body-parser';
import cors from 'cors';
import dotenv from 'dotenv';
import express from 'express';
import path from 'path';
import { fileURLToPath } from 'url';
dotenv.config();
// Configuration
const SERVER_PORT = process.env.SERVER_PORT || 6080;
const OV_MEET_SERVER_URL = process.env.OV_MEET_SERVER_URL || 'http://localhost:9080';
const OV_MEET_API_KEY = process.env.OV_MEET_API_KEY || 'meet-api-key';
const app = express();
app.use(cors());
app.use(express.json());
app.use(bodyParser.urlencoded({ extended: true }));
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
app.use(express.static(path.join(__dirname, '../static')));
// Create a new room
app.post('/rooms', async (req, res) => {
const { roomName } = req.body;
if (!roomName) {
res.status(400).json({ message: `'roomName' is required` });
return;
}
try {
// Create a new OpenVidu Meet room using the API
const room = await httpRequest('POST', 'rooms', {
roomName,
autoDeletionDate: Date.now() + 2 * 60 * 60 * 1000, // Room will be deleted after 2 hours
config: {
chat: {
enabled: true
},
recording: {
enabled: true,
allowAccessTo: 'admin_moderator_speaker'
},
virtualBackground: {
enabled: true
}
}
});
console.log('Room created:', room);
res.status(201).json({ message: `Room '${roomName}' created successfully`, room });
} catch (error) {
handleApiError(res, error, `Error creating room '${roomName}'`);
}
});
// Start the server
app.listen(SERVER_PORT, () => {
console.log(`Server listening on http://localhost:${SERVER_PORT}`);
});
// Function to make HTTP requests to OpenVidu Meet API
const httpRequest = async (method, path, body) => {
const response = await fetch(`${OV_MEET_SERVER_URL}/api/v1/${path}`, {
method,
headers: {
'Content-Type': 'application/json',
'X-API-KEY': OV_MEET_API_KEY // Include the API key in the header for authentication
},
body: body ? JSON.stringify(body) : undefined
});
const responseBody = await response.json();
if (!response.ok) {
console.error('Error while performing request to OpenVidu Meet API:', responseBody);
// Create an error object that includes the HTTP status code from the API
const error = new Error(responseBody.message || 'Failed to perform request to OpenVidu Meet API');
error.statusCode = response.status;
throw error;
}
return responseBody;
};
// Helper function to handle API errors consistently
const handleApiError = (res, error, message) => {
console.error(`${message}: ${error.message}`);
const statusCode = error.statusCode || 500;
const errorMessage = error.statusCode ? error.message : message;
res.status(statusCode).json({ message: errorMessage });
};

View File

@ -0,0 +1,146 @@
/* General layout */
body {
margin: 0;
font-family: 'Roboto', 'RobotoDraft', Helvetica, Arial, sans-serif;
background-color: #29292e;
color: #e5e7eb;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
}
/* Container */
.container {
width: 90%;
max-width: 500px;
padding: 2rem;
box-sizing: border-box;
}
/* Header */
.header {
text-align: center;
margin-bottom: 2rem;
}
.logo {
height: 150px;
}
.subtitle {
color: #b0b0b5;
font-size: 1.2rem;
line-height: 1.4;
}
/* Card */
.card {
background-color: #242429;
border-radius: 12px;
padding: 2rem;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.25);
}
.card h2 {
margin-top: 0;
color: #f3f4f6;
text-align: center;
}
/* Form */
form {
display: flex;
flex-direction: column;
margin-top: 1.5rem;
}
label {
font-size: 0.9rem;
color: #c5c6ca;
margin-bottom: 0.3rem;
}
.required {
color: #ef5350;
}
input[type='text'] {
background-color: #1b1b1f;
border: 1px solid #3c3c44;
color: #f9fafb;
padding: 0.75rem;
border-radius: 8px;
outline: none;
font-size: 1rem;
margin-bottom: 1.2rem;
transition: border-color 0.2s ease;
}
input[type='text']:focus {
border-color: #cecece;
}
.error-message {
color: #f87171;
font-size: 0.85rem;
margin-top: 0;
margin-bottom: 1rem;
height: 1rem;
}
/* Button */
button {
background-color: #4caf50;
color: white;
font-weight: 600;
border: none;
padding: 0.9rem;
border-radius: 8px;
cursor: pointer;
font-size: 1rem;
transition: background-color 0.2s;
}
button:hover:enabled {
background-color: #43a047;
}
button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* Footer */
.footer {
text-align: center;
margin-top: 2rem;
color: #9ca3af;
font-size: 0.8rem;
}
/* Responsive adjustments */
@media (max-width: 480px) {
.container {
max-width: 90%;
padding: 1.5rem;
}
.subtitle {
font-size: 1rem;
}
.card {
padding: 1.5rem;
}
.logo {
height: 100px;
}
button {
padding: 0.8rem;
font-size: 0.95rem;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

View File

@ -0,0 +1,34 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>OpenVidu Meet Demo</title>
<link rel="shortcut icon" href="images/favicon.ico" type="image/x-icon" />
<link rel="stylesheet" href="css/styles.css" />
</head>
<body>
<div class="container">
<header class="header">
<img src="images/meet_logo.png" alt="OpenVidu Meet Logo" class="logo" />
<p class="subtitle">Create video-call rooms with a single click and test OpenVidu Meet features</p>
</header>
<main class="card">
<h2>Create Room</h2>
<form id="create-room-form" novalidate>
<label for="roomName">Room Name <span class="required">*</span></label>
<input type="text" id="roomName" name="roomName" placeholder="Enter room name" required />
<p class="error-message" id="error-message" hidden></p>
<button type="submit" id="createRoomBtn" disabled>+ Create Room</button>
</form>
</main>
<footer class="footer">
<p>OpenVidu Meet Demo — v3.4.1 (CE)</p>
</footer>
</div>
<script src="js/app.js"></script>
</body>
</html>

View File

@ -0,0 +1,65 @@
// Get references to HTML elements
const form = document.getElementById('create-room-form');
const input = document.getElementById('roomName');
const button = document.getElementById('createRoomBtn');
const errorMessage = document.getElementById('error-message');
// Handle form changes to enable/disable button
input.addEventListener('input', () => {
const hasValue = input.value.trim() !== '';
button.disabled = !hasValue;
if (hasValue) {
errorMessage.textContent = '';
errorMessage.hidden = true;
} else {
errorMessage.textContent = 'Room name is required.';
errorMessage.hidden = false;
}
});
// Handle form submission
form.addEventListener('submit', (event) => {
event.preventDefault();
createRoom();
});
// Function to create a new room by calling the backend API
async function createRoom() {
// Clear previous error message
errorMessage.textContent = '';
errorMessage.hidden = true;
try {
const roomName = input.value;
const { room } = await httpRequest('POST', '/rooms', {
roomName
});
// Redirect to the newly created room
window.location.href = room.moderatorUrl;
} catch (error) {
console.error('Error creating room:', error.message);
errorMessage.textContent = 'Error creating room';
errorMessage.hidden = false;
}
}
// Function to make HTTP requests to the backend
async function httpRequest(method, path, body) {
const response = await fetch(path, {
method,
headers: {
'Content-Type': 'application/json'
},
body: body ? JSON.stringify(body) : undefined
});
const responseBody = await response.json();
if (!response.ok) {
throw new Error(responseBody.message || 'Failed to perform request to backend');
}
return responseBody;
}