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