Add OpenVidu Meet demo app
This commit is contained in:
parent
054bd1594e
commit
bad2a735b6
3
meet-demo/.env
Normal file
3
meet-demo/.env
Normal 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
24
meet-demo/Dockerfile
Normal 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
65
meet-demo/README.md
Normal 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
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
14
meet-demo/package.json
Normal 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
95
meet-demo/src/index.js
Normal 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 });
|
||||||
|
};
|
||||||
146
meet-demo/static/css/styles.css
Normal file
146
meet-demo/static/css/styles.css
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
meet-demo/static/images/favicon.ico
Normal file
BIN
meet-demo/static/images/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.3 KiB |
BIN
meet-demo/static/images/meet_logo.png
Normal file
BIN
meet-demo/static/images/meet_logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.2 KiB |
34
meet-demo/static/index.html
Normal file
34
meet-demo/static/index.html
Normal 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>
|
||||||
65
meet-demo/static/js/app.js
Normal file
65
meet-demo/static/js/app.js
Normal 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;
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user