feat: backend web-sockets solution
This commit is contained in:
@@ -18,3 +18,15 @@ Before running, make sure you have `ffmpeg` installed on your system.
|
||||
node index.js
|
||||
```
|
||||
Be aware, that this application is designed for Linux systems!
|
||||
|
||||
## Architecture
|
||||
|
||||
### API
|
||||
|
||||
- Endpoints to add a channel, get all channels, get selected channel and set selected channel
|
||||
|
||||
### WebSockets
|
||||
|
||||
- `channel-added` and `channel-selected` events will be send to all connected clients
|
||||
- chat messages: `send-chat-message` and `chat-message`
|
||||
- users: `user-connected` and `user-disconnected`
|
||||
|
||||
@@ -1,66 +1,11 @@
|
||||
const ffmpegService = require('../services/FFmpegService');
|
||||
const storageService = require('../services/StorageService');
|
||||
const Channel = require('../models/Channel');
|
||||
|
||||
let channels = [new Channel('DEFAULT_CHANNEL', process.env.DEFAULT_CHANNEL_URL)];
|
||||
let currentChannel = channels[0];
|
||||
|
||||
function setCurrent(req, res) {
|
||||
const { channelName } = req.body || {};
|
||||
|
||||
if (!channelName) {
|
||||
return res.status(400).json({ status: 'error', message: 'channelName is required' });
|
||||
}
|
||||
|
||||
const nextChannel = channels.find(channel => channel.name === channelName);
|
||||
if (!nextChannel) {
|
||||
res.status(400).json({ status: 'error', message: 'Channel does not exist' });
|
||||
}
|
||||
|
||||
if (currentChannel !== nextChannel) {
|
||||
const segmentNumber = storageService.getNextSegmentNumber();
|
||||
storageService.clearStorage();
|
||||
currentChannel = nextChannel;
|
||||
ffmpegService.startFFmpeg(nextChannel.url, segmentNumber);
|
||||
}
|
||||
res.status(200).json({ status: 'success', channelUrl: currentChannel.url });
|
||||
}
|
||||
|
||||
|
||||
function getCurrent(_, res) {
|
||||
if (currentChannel) {
|
||||
res.status(200).json({ channelName: currentChannel.name, channelUrl: currentChannel.url });
|
||||
} else {
|
||||
res.status(404).json({ status: 'error', message: 'No channel active' });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function getChannels(_, res) {
|
||||
res.status(200).json({ channels });
|
||||
}
|
||||
|
||||
function addChannel(req, res) {
|
||||
const { name, url } = req.body;
|
||||
|
||||
if (!name || !url) {
|
||||
return res.status(400).json({ status: 'error', message: 'Channel name and URL are required' });
|
||||
}
|
||||
|
||||
const channelExists = channels.some(channel => channel.url === url);
|
||||
if (channelExists) {
|
||||
return res.status(409).json({ status: 'error', message: 'Channel already exists' });
|
||||
}
|
||||
|
||||
const newChannel = new Channel(name, url);
|
||||
channels.push(newChannel);
|
||||
res.status(201).json({ status: 'success', message: 'Channel added', channel: newChannel });
|
||||
}
|
||||
|
||||
const ChannelService = require('../services/ChannelService');
|
||||
|
||||
module.exports = {
|
||||
setCurrent,
|
||||
getCurrent,
|
||||
getChannels,
|
||||
addChannel
|
||||
};
|
||||
getChannels(req, res) {
|
||||
res.json(ChannelService.getChannels());
|
||||
},
|
||||
|
||||
getCurrentChannel(req, res) {
|
||||
res.json(ChannelService.getCurrentChannel());
|
||||
},
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
const ffmpegService = require('../services/FFmpegService');
|
||||
const storageService = require('../services/StorageService');
|
||||
const ffmpegService = require('../services/streaming/FFmpegService');
|
||||
const storageService = require('../services/streaming/StorageService');
|
||||
|
||||
function start() {
|
||||
if (!ffmpegService.isFFmpegRunning()) {
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
const express = require('express');
|
||||
const dotenv = require('dotenv');
|
||||
|
||||
const channelController = require('./controllers/ChannelController');
|
||||
const streamController = require('./controllers/StreamController');
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
|
||||
|
||||
const apiRouter = express.Router();
|
||||
|
||||
apiRouter.post('/current', channelController.setCurrent);
|
||||
apiRouter.get('/current', channelController.getCurrent);
|
||||
|
||||
apiRouter.get('/', channelController.getChannels);
|
||||
apiRouter.post('/add', channelController.addChannel);
|
||||
|
||||
app.use('/channels', apiRouter);
|
||||
|
||||
const PORT = 5000;
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Server listening on Port ${PORT}`);
|
||||
streamController.start();
|
||||
});
|
||||
240
backend/package-lock.json
generated
240
backend/package-lock.json
generated
@@ -11,7 +11,34 @@
|
||||
"dependencies": {
|
||||
"child_process": "^1.0.2",
|
||||
"dotenv": "^16.4.5",
|
||||
"express": "^4.21.1"
|
||||
"express": "^4.21.1",
|
||||
"socket.io": "^4.8.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@socket.io/component-emitter": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
|
||||
"integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA=="
|
||||
},
|
||||
"node_modules/@types/cookie": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz",
|
||||
"integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q=="
|
||||
},
|
||||
"node_modules/@types/cors": {
|
||||
"version": "2.8.17",
|
||||
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz",
|
||||
"integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "22.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.0.tgz",
|
||||
"integrity": "sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.19.8"
|
||||
}
|
||||
},
|
||||
"node_modules/accepts": {
|
||||
@@ -31,6 +58,14 @@
|
||||
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
|
||||
"integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
|
||||
},
|
||||
"node_modules/base64id": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
|
||||
"integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==",
|
||||
"engines": {
|
||||
"node": "^4.5.0 || >= 5.9"
|
||||
}
|
||||
},
|
||||
"node_modules/body-parser": {
|
||||
"version": "1.20.3",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
|
||||
@@ -117,6 +152,18 @@
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
|
||||
},
|
||||
"node_modules/cors": {
|
||||
"version": "2.8.5",
|
||||
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
|
||||
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
|
||||
"dependencies": {
|
||||
"object-assign": "^4",
|
||||
"vary": "^1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
@@ -182,6 +229,63 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/engine.io": {
|
||||
"version": "6.6.2",
|
||||
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.2.tgz",
|
||||
"integrity": "sha512-gmNvsYi9C8iErnZdVcJnvCpSKbWTt1E8+JZo8b+daLninywUWi5NQ5STSHZ9rFjFO7imNcvb8Pc5pe/wMR5xEw==",
|
||||
"dependencies": {
|
||||
"@types/cookie": "^0.4.1",
|
||||
"@types/cors": "^2.8.12",
|
||||
"@types/node": ">=10.0.0",
|
||||
"accepts": "~1.3.4",
|
||||
"base64id": "2.0.0",
|
||||
"cookie": "~0.7.2",
|
||||
"cors": "~2.8.5",
|
||||
"debug": "~4.3.1",
|
||||
"engine.io-parser": "~5.2.1",
|
||||
"ws": "~8.17.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/engine.io-parser": {
|
||||
"version": "5.2.3",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
|
||||
"integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/engine.io/node_modules/cookie": {
|
||||
"version": "0.7.2",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
|
||||
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/engine.io/node_modules/debug": {
|
||||
"version": "4.3.7",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
||||
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/engine.io/node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
||||
},
|
||||
"node_modules/es-define-property": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
|
||||
@@ -475,6 +579,14 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/object-inspect": {
|
||||
"version": "1.13.2",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz",
|
||||
@@ -670,6 +782,107 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io": {
|
||||
"version": "4.8.1",
|
||||
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz",
|
||||
"integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==",
|
||||
"dependencies": {
|
||||
"accepts": "~1.3.4",
|
||||
"base64id": "~2.0.0",
|
||||
"cors": "~2.8.5",
|
||||
"debug": "~4.3.2",
|
||||
"engine.io": "~6.6.0",
|
||||
"socket.io-adapter": "~2.5.2",
|
||||
"socket.io-parser": "~4.2.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io-adapter": {
|
||||
"version": "2.5.5",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz",
|
||||
"integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==",
|
||||
"dependencies": {
|
||||
"debug": "~4.3.4",
|
||||
"ws": "~8.17.1"
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io-adapter/node_modules/debug": {
|
||||
"version": "4.3.7",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
||||
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io-adapter/node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
||||
},
|
||||
"node_modules/socket.io-parser": {
|
||||
"version": "4.2.4",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
|
||||
"integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
|
||||
"dependencies": {
|
||||
"@socket.io/component-emitter": "~3.1.0",
|
||||
"debug": "~4.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io-parser/node_modules/debug": {
|
||||
"version": "4.3.7",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
||||
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io-parser/node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
||||
},
|
||||
"node_modules/socket.io/node_modules/debug": {
|
||||
"version": "4.3.7",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
||||
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/socket.io/node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
|
||||
},
|
||||
"node_modules/statuses": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
||||
@@ -698,6 +911,11 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.19.8",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
|
||||
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="
|
||||
},
|
||||
"node_modules/unpipe": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||
@@ -721,6 +939,26 @@
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.17.1",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
|
||||
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": ">=5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
"name": "iptv-restream",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
"start": "node server.js",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"repository": {
|
||||
@@ -20,6 +21,7 @@
|
||||
"dependencies": {
|
||||
"child_process": "^1.0.2",
|
||||
"dotenv": "^16.4.5",
|
||||
"express": "^4.21.1"
|
||||
"express": "^4.21.1",
|
||||
"socket.io": "^4.8.1"
|
||||
}
|
||||
}
|
||||
|
||||
47
backend/server.js
Normal file
47
backend/server.js
Normal file
@@ -0,0 +1,47 @@
|
||||
const express = require('express');
|
||||
const dotenv = require('dotenv');
|
||||
|
||||
const channelController = require('./controllers/ChannelController');
|
||||
const streamController = require('./controllers/StreamController');
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
|
||||
const apiRouter = express.Router();
|
||||
|
||||
apiRouter.get('/', channelController.getChannels);
|
||||
apiRouter.get('/current', channelController.getCurrentChannel);
|
||||
|
||||
app.use('/channels', apiRouter);
|
||||
|
||||
const PORT = 5000;
|
||||
const server = app.listen(PORT, () => {
|
||||
console.log(`Server listening on Port ${PORT}`);
|
||||
streamController.start();
|
||||
});
|
||||
|
||||
|
||||
// Web Sockets
|
||||
const io = require('socket.io')(server)
|
||||
|
||||
const connectedUsers = {}
|
||||
|
||||
io.on('connection', socket => {
|
||||
|
||||
socket.on('new-user', name => {
|
||||
connectedUsers[socket.id] = name
|
||||
socket.broadcast.emit('user-connected', name)
|
||||
})
|
||||
|
||||
socket.on('disconnect', () => {
|
||||
socket.broadcast.emit('user-disconnected', connectedUsers[socket.id])
|
||||
delete connectedUsers[socket.id]
|
||||
})
|
||||
|
||||
ChannelSocketHandler(io, socket);
|
||||
|
||||
ChatSocketHandler(io, socket);
|
||||
|
||||
})
|
||||
45
backend/services/ChannelService.js
Normal file
45
backend/services/ChannelService.js
Normal file
@@ -0,0 +1,45 @@
|
||||
const ffmpegService = require('./streaming/FFmpegService');
|
||||
const storageService = require('./streaming/StorageService');
|
||||
const Channel = require('../models/Channel');
|
||||
|
||||
class ChannelService {
|
||||
constructor() {
|
||||
this.channels = [new Channel('DEFAULT_CHANNEL', process.env.DEFAULT_CHANNEL_URL)];
|
||||
this.currentChannel = this.channels[0];
|
||||
}
|
||||
|
||||
getChannels() {
|
||||
return this.channels;
|
||||
}
|
||||
|
||||
addChannel(name, url) {
|
||||
const existing = this.channels.some(channel => channel.url === url);
|
||||
if (existing) {
|
||||
throw new Error('Channel already exists');
|
||||
}
|
||||
const newChannel = new Channel(name, url);
|
||||
this.channels.push(newChannel);
|
||||
|
||||
return newChannel;
|
||||
}
|
||||
|
||||
setCurrentChannel(name) {
|
||||
const nextChannel = this.channels.find(channel => channel.name === name);
|
||||
if (!nextChannel) {
|
||||
throw new Error('Channel does not exist');
|
||||
}
|
||||
if (currentChannel !== nextChannel) {
|
||||
const segmentNumber = storageService.getNextSegmentNumber();
|
||||
storageService.clearStorage();
|
||||
currentChannel = nextChannel;
|
||||
ffmpegService.startFFmpeg(nextChannel.url, segmentNumber);
|
||||
}
|
||||
return nextChannel;
|
||||
}
|
||||
|
||||
getCurrentChannel() {
|
||||
return this.currentChannel;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new ChannelService();
|
||||
21
backend/services/ChatService.js
Normal file
21
backend/services/ChatService.js
Normal file
@@ -0,0 +1,21 @@
|
||||
const ChatMessage = require('../models/ChatMessage');
|
||||
|
||||
|
||||
// At the moment, this service is not used! It is only a placeholder for future development for a persistent chat.
|
||||
class ChatService {
|
||||
constructor() {
|
||||
this.messages = [];
|
||||
}
|
||||
|
||||
addMessage(user, message) {
|
||||
const newMessage = new ChatMessage(user, message);
|
||||
this.messages.push(newMessage);
|
||||
return newMessage;
|
||||
}
|
||||
|
||||
getMessages() {
|
||||
return this.messages;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new ChatService();
|
||||
14
backend/services/SocketService.js
Normal file
14
backend/services/SocketService.js
Normal file
@@ -0,0 +1,14 @@
|
||||
let io;
|
||||
|
||||
function setSocketIO(socketIOInstance) {
|
||||
io = socketIOInstance;
|
||||
}
|
||||
|
||||
function getSocketIO() {
|
||||
if (!io) {
|
||||
throw new Error('Socket.IO instance not initialized');
|
||||
}
|
||||
return io;
|
||||
}
|
||||
|
||||
module.exports = { setSocketIO, getSocketIO };
|
||||
23
backend/socket/ChannelSocketHandler.js
Normal file
23
backend/socket/ChannelSocketHandler.js
Normal file
@@ -0,0 +1,23 @@
|
||||
const ChannelService = require('../services/ChannelService');
|
||||
|
||||
module.exports = (io, socket) => {
|
||||
|
||||
socket.on('add-channel', ({ name, url }) => {
|
||||
try {
|
||||
const newChannel = ChannelService.addChannel(name, url);
|
||||
io.emit('channel-added', newChannel); // Broadcast to all clients
|
||||
} catch (err) {
|
||||
socket.emit('error', { message: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
socket.on('set-current-channel', (name) => {
|
||||
try {
|
||||
const currentChannel = ChannelService.setCurrentChannel(name);
|
||||
io.emit('channel-selected', currentChannel); // Broadcast to all clients
|
||||
} catch (err) {
|
||||
socket.emit('error', { message: err.message });
|
||||
}
|
||||
});
|
||||
};
|
||||
8
backend/socket/ChatSocketHandler.js
Normal file
8
backend/socket/ChatSocketHandler.js
Normal file
@@ -0,0 +1,8 @@
|
||||
const ChatService = require('../services/ChatService');
|
||||
|
||||
module.exports = (io, socket) => {
|
||||
socket.on('send-message', ({ user, message }) => {
|
||||
ChatService.addMessage(user, message);
|
||||
socket.broadcast.emit('chat-message', { message: message, name: user }) // Broadcast to all clients except sender
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user