[mirotalksfu] - first release
هذا الالتزام موجود في:
25
src/Logger.js
Normal file
25
src/Logger.js
Normal file
@@ -0,0 +1,25 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = class Logger {
|
||||
constructor(appName, debugOn = true) {
|
||||
if (appName) this.appName = appName;
|
||||
else this.appName = 'mirotalk';
|
||||
this.debugOn = debugOn;
|
||||
}
|
||||
|
||||
debug(msg, op = '') {
|
||||
if (this.debugOn === false) return;
|
||||
let dataTime = new Date().toISOString().replace(/T/, ' ').replace(/Z/, '');
|
||||
console.log('[' + dataTime + '] [' + this.appName + '] ' + msg, op);
|
||||
}
|
||||
|
||||
warn(msg, op = '') {
|
||||
let dataTime = new Date().toISOString().replace(/T/, ' ').replace(/Z/, '');
|
||||
console.warn('[' + dataTime + '] [' + this.appName + '] ' + msg, op);
|
||||
}
|
||||
|
||||
error(msg, op = '') {
|
||||
let dataTime = new Date().toISOString().replace(/T/, ' ').replace(/Z/, '');
|
||||
console.error('[' + dataTime + '] [' + this.appName + '] ' + msg, op);
|
||||
}
|
||||
};
|
||||
133
src/Peer.js
Normal file
133
src/Peer.js
Normal file
@@ -0,0 +1,133 @@
|
||||
'use strict';
|
||||
|
||||
const Logger = require('./Logger');
|
||||
const log = new Logger('Peer');
|
||||
|
||||
module.exports = class Peer {
|
||||
constructor(socket_id, data) {
|
||||
this.id = socket_id;
|
||||
this.peer_name = data.peer_name;
|
||||
this.peer_audio = data.peer_audio;
|
||||
this.peer_video = data.peer_video;
|
||||
this.peer_info = data.peer_info;
|
||||
this.transports = new Map();
|
||||
this.consumers = new Map();
|
||||
this.producers = new Map();
|
||||
}
|
||||
|
||||
// ####################################################
|
||||
// TRANSPORT
|
||||
// ####################################################
|
||||
|
||||
addTransport(transport) {
|
||||
this.transports.set(transport.id, transport);
|
||||
}
|
||||
|
||||
async connectTransport(transport_id, dtlsParameters) {
|
||||
if (!this.transports.has(transport_id)) return;
|
||||
|
||||
await this.transports.get(transport_id).connect({
|
||||
dtlsParameters: dtlsParameters,
|
||||
});
|
||||
}
|
||||
|
||||
close() {
|
||||
this.transports.forEach((transport) => transport.close());
|
||||
}
|
||||
|
||||
// ####################################################
|
||||
// PRODUCER
|
||||
// ####################################################
|
||||
|
||||
getProducer(producer_id) {
|
||||
return this.producers.get(producer_id);
|
||||
}
|
||||
|
||||
async createProducer(producerTransportId, rtpParameters, kind) {
|
||||
let producer = await this.transports.get(producerTransportId).produce({
|
||||
kind,
|
||||
rtpParameters,
|
||||
});
|
||||
|
||||
this.producers.set(producer.id, producer);
|
||||
|
||||
producer.on(
|
||||
'transportclose',
|
||||
function () {
|
||||
log.debug('Producer transport close', {
|
||||
peer_name: this.peer_name,
|
||||
consumer_id: producer.id,
|
||||
});
|
||||
producer.close();
|
||||
this.producers.delete(producer.id);
|
||||
}.bind(this),
|
||||
);
|
||||
|
||||
return producer;
|
||||
}
|
||||
|
||||
closeProducer(producer_id) {
|
||||
try {
|
||||
this.producers.get(producer_id).close();
|
||||
} catch (ex) {
|
||||
log.warn('Close Producer', ex);
|
||||
}
|
||||
this.producers.delete(producer_id);
|
||||
}
|
||||
|
||||
// ####################################################
|
||||
// CONSUMER
|
||||
// ####################################################
|
||||
|
||||
async createConsumer(consumer_transport_id, producer_id, rtpCapabilities) {
|
||||
let consumerTransport = this.transports.get(consumer_transport_id);
|
||||
let consumer = null;
|
||||
|
||||
try {
|
||||
consumer = await consumerTransport.consume({
|
||||
producerId: producer_id,
|
||||
rtpCapabilities,
|
||||
paused: false,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Consume failed', error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (consumer.type === 'simulcast') {
|
||||
await consumer.setPreferredLayers({
|
||||
spatialLayer: 2,
|
||||
temporalLayer: 2,
|
||||
});
|
||||
}
|
||||
|
||||
this.consumers.set(consumer.id, consumer);
|
||||
|
||||
consumer.on(
|
||||
'transportclose',
|
||||
function () {
|
||||
log.debug('Consumer transport close', {
|
||||
peer_name: this.peer_name,
|
||||
consumer_id: consumer.id,
|
||||
});
|
||||
this.consumers.delete(consumer.id);
|
||||
}.bind(this),
|
||||
);
|
||||
|
||||
return {
|
||||
consumer,
|
||||
params: {
|
||||
producerId: producer_id,
|
||||
id: consumer.id,
|
||||
kind: consumer.kind,
|
||||
rtpParameters: consumer.rtpParameters,
|
||||
type: consumer.type,
|
||||
producerPaused: consumer.producerPaused,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
removeConsumer(consumer_id) {
|
||||
this.consumers.delete(consumer_id);
|
||||
}
|
||||
};
|
||||
203
src/Room.js
Normal file
203
src/Room.js
Normal file
@@ -0,0 +1,203 @@
|
||||
'use strict';
|
||||
|
||||
const config = require('./config');
|
||||
const Logger = require('./Logger');
|
||||
const log = new Logger('Room');
|
||||
|
||||
module.exports = class Room {
|
||||
constructor(room_id, worker, io) {
|
||||
this.id = room_id;
|
||||
this.worker = worker;
|
||||
this.router = null;
|
||||
this.io = io;
|
||||
this.peers = new Map();
|
||||
this.createTheRouter();
|
||||
}
|
||||
|
||||
// ####################################################
|
||||
// ROUTER
|
||||
// ####################################################
|
||||
|
||||
createTheRouter() {
|
||||
const mediaCodecs = config.mediasoup.router.mediaCodecs;
|
||||
this.worker
|
||||
.createRouter({
|
||||
mediaCodecs,
|
||||
})
|
||||
.then(
|
||||
function (router) {
|
||||
this.router = router;
|
||||
}.bind(this),
|
||||
);
|
||||
}
|
||||
|
||||
getRtpCapabilities() {
|
||||
return this.router.rtpCapabilities;
|
||||
}
|
||||
|
||||
// ####################################################
|
||||
// PEERS
|
||||
// ####################################################
|
||||
|
||||
addPeer(peer) {
|
||||
this.peers.set(peer.id, peer);
|
||||
}
|
||||
|
||||
getPeers() {
|
||||
return this.peers;
|
||||
}
|
||||
|
||||
toJson() {
|
||||
return {
|
||||
id: this.id,
|
||||
peers: JSON.stringify([...this.peers]),
|
||||
};
|
||||
}
|
||||
|
||||
getProducerListForPeer() {
|
||||
let producerList = [];
|
||||
this.peers.forEach((peer) => {
|
||||
peer.producers.forEach((producer) => {
|
||||
producerList.push({
|
||||
producer_id: producer.id,
|
||||
peer_name: peer.peer_name,
|
||||
peer_info: peer.peer_info,
|
||||
});
|
||||
});
|
||||
});
|
||||
return producerList;
|
||||
}
|
||||
|
||||
async connectPeerTransport(socket_id, transport_id, dtlsParameters) {
|
||||
if (!this.peers.has(socket_id)) return;
|
||||
await this.peers.get(socket_id).connectTransport(transport_id, dtlsParameters);
|
||||
}
|
||||
|
||||
async removePeer(socket_id) {
|
||||
this.peers.get(socket_id).close();
|
||||
this.peers.delete(socket_id);
|
||||
}
|
||||
|
||||
// ####################################################
|
||||
// WEBRTC TRANSPORT
|
||||
// ####################################################
|
||||
|
||||
async createWebRtcTransport(socket_id) {
|
||||
const { maxIncomingBitrate, initialAvailableOutgoingBitrate } = config.mediasoup.webRtcTransport;
|
||||
|
||||
const transport = await this.router.createWebRtcTransport({
|
||||
listenIps: config.mediasoup.webRtcTransport.listenIps,
|
||||
enableUdp: true,
|
||||
enableTcp: true,
|
||||
preferUdp: true,
|
||||
initialAvailableOutgoingBitrate,
|
||||
});
|
||||
|
||||
if (maxIncomingBitrate) {
|
||||
try {
|
||||
await transport.setMaxIncomingBitrate(maxIncomingBitrate);
|
||||
} catch (error) {}
|
||||
}
|
||||
|
||||
transport.on(
|
||||
'dtlsstatechange',
|
||||
function (dtlsState) {
|
||||
if (dtlsState === 'closed') {
|
||||
log.debug('Transport close', { peer_name: this.peers.get(socket_id).peer_name });
|
||||
transport.close();
|
||||
}
|
||||
}.bind(this),
|
||||
);
|
||||
|
||||
transport.on('close', () => {
|
||||
log.debug('Transport close', { peer_name: this.peers.get(socket_id).peer_name });
|
||||
});
|
||||
|
||||
log.debug('Adding transport', { transportId: transport.id });
|
||||
this.peers.get(socket_id).addTransport(transport);
|
||||
return {
|
||||
params: {
|
||||
id: transport.id,
|
||||
iceParameters: transport.iceParameters,
|
||||
iceCandidates: transport.iceCandidates,
|
||||
dtlsParameters: transport.dtlsParameters,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// ####################################################
|
||||
// PRODUCE
|
||||
// ####################################################
|
||||
|
||||
async produce(socket_id, producerTransportId, rtpParameters, kind) {
|
||||
return new Promise(
|
||||
async function (resolve, reject) {
|
||||
let producer = await this.peers.get(socket_id).createProducer(producerTransportId, rtpParameters, kind);
|
||||
resolve(producer.id);
|
||||
this.broadCast(socket_id, 'newProducers', [
|
||||
{
|
||||
producer_id: producer.id,
|
||||
producer_socket_id: socket_id,
|
||||
peer_name: this.peers.get(socket_id).peer_name,
|
||||
},
|
||||
]);
|
||||
}.bind(this),
|
||||
);
|
||||
}
|
||||
|
||||
// ####################################################
|
||||
// CONSUME
|
||||
// ####################################################
|
||||
|
||||
async consume(socket_id, consumer_transport_id, producer_id, rtpCapabilities) {
|
||||
if (
|
||||
!this.router.canConsume({
|
||||
producerId: producer_id,
|
||||
rtpCapabilities,
|
||||
})
|
||||
) {
|
||||
log.error('can not consume');
|
||||
return;
|
||||
}
|
||||
|
||||
let { consumer, params } = await this.peers
|
||||
.get(socket_id)
|
||||
.createConsumer(consumer_transport_id, producer_id, rtpCapabilities);
|
||||
|
||||
consumer.on(
|
||||
'producerclose',
|
||||
function () {
|
||||
log.debug('Consumer closed due to producerclose event', {
|
||||
peer_name: this.peers.get(socket_id).peer_name,
|
||||
consumer_id: consumer.id,
|
||||
});
|
||||
this.peers.get(socket_id).removeConsumer(consumer.id);
|
||||
|
||||
// tell client consumer is dead
|
||||
this.io.to(socket_id).emit('consumerClosed', {
|
||||
consumer_id: consumer.id,
|
||||
});
|
||||
}.bind(this),
|
||||
);
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
closeProducer(socket_id, producer_id) {
|
||||
this.peers.get(socket_id).closeProducer(producer_id);
|
||||
}
|
||||
|
||||
// ####################################################
|
||||
// SENDER
|
||||
// ####################################################
|
||||
|
||||
broadCast(socket_id, peer_name, data) {
|
||||
for (let otherID of Array.from(this.peers.keys()).filter((id) => id !== socket_id)) {
|
||||
this.send(otherID, peer_name, data);
|
||||
}
|
||||
}
|
||||
|
||||
send(socket_id, peer_name, data) {
|
||||
this.io.to(socket_id).emit(peer_name, data);
|
||||
}
|
||||
};
|
||||
362
src/Server.js
Normal file
362
src/Server.js
Normal file
@@ -0,0 +1,362 @@
|
||||
'use strict';
|
||||
|
||||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
const app = express();
|
||||
const compression = require('compression');
|
||||
const https = require('httpolyglot');
|
||||
const fs = require('fs');
|
||||
const mediasoup = require('mediasoup');
|
||||
const config = require('./config');
|
||||
const path = require('path');
|
||||
const ngrok = require('ngrok');
|
||||
const Room = require('./Room');
|
||||
const Peer = require('./Peer');
|
||||
const ServerApi = require('./ServerApi');
|
||||
const Logger = require('./Logger');
|
||||
const log = new Logger('Server');
|
||||
|
||||
const options = {
|
||||
key: fs.readFileSync(path.join(__dirname, config.sslKey), 'utf-8'),
|
||||
cert: fs.readFileSync(path.join(__dirname, config.sslCrt), 'utf-8'),
|
||||
};
|
||||
|
||||
const httpsServer = https.createServer(options, app);
|
||||
const io = require('socket.io')(httpsServer);
|
||||
const localHost = 'https://' + 'localhost' + ':' + config.listenPort; // config.listenIp
|
||||
|
||||
// all mediasoup workers
|
||||
let workers = [];
|
||||
let nextMediasoupWorkerIdx = 0;
|
||||
|
||||
// all Room lists
|
||||
let roomList = new Map();
|
||||
|
||||
app.use(cors());
|
||||
app.use(compression());
|
||||
app.use(express.static(path.join(__dirname, '..', 'public')));
|
||||
|
||||
// Remove trailing slashes in url handle bad requests
|
||||
app.use((err, req, res, next) => {
|
||||
if (err instanceof SyntaxError && err.status === 400 && 'body' in err) {
|
||||
log.debug('Request Error', {
|
||||
header: req.headers,
|
||||
body: req.body,
|
||||
error: err.message,
|
||||
});
|
||||
return res.status(400).send({ status: 404, message: err.message }); // Bad request
|
||||
}
|
||||
if (req.path.substr(-1) === '/' && req.path.length > 1) {
|
||||
let query = req.url.slice(req.path.length);
|
||||
res.redirect(301, req.path.slice(0, -1) + query);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
});
|
||||
|
||||
// all start from here
|
||||
app.get(['/'], (req, res) => {
|
||||
res.sendFile(path.join(__dirname, '..', 'public/landing.html'));
|
||||
});
|
||||
|
||||
// set new room name and join
|
||||
app.get(['/newroom'], (req, res) => {
|
||||
res.sendFile(path.join(__dirname, '..', 'public/newroom.html'));
|
||||
});
|
||||
|
||||
// if not allow video/audio
|
||||
app.get(['/permission'], (req, res) => {
|
||||
res.sendFile(path.join(__dirname, '..', 'public/permission.html'));
|
||||
});
|
||||
|
||||
// no room name specified to join
|
||||
app.get('/join/', (req, res) => {
|
||||
res.redirect('/');
|
||||
});
|
||||
|
||||
// join to room
|
||||
app.get('/join/*', (req, res) => {
|
||||
if (Object.keys(req.query).length > 0) {
|
||||
log.debug('redirect:' + req.url + ' to ' + url.parse(req.url).pathname);
|
||||
res.redirect(url.parse(req.url).pathname);
|
||||
} else {
|
||||
res.sendFile(path.join(__dirname, '..', 'public/Room.html'));
|
||||
}
|
||||
});
|
||||
|
||||
// ####################################################
|
||||
// API
|
||||
// ####################################################
|
||||
|
||||
// Api parse body data as json
|
||||
app.use(express.json());
|
||||
|
||||
// request meeting room endpoint
|
||||
app.post(['/api/v1/meeting'], (req, res) => {
|
||||
// check if user was authorized for the api call
|
||||
let host = req.headers.host;
|
||||
let authorization = req.headers.authorization;
|
||||
let api = new ServerApi(host, authorization);
|
||||
if (!api.isAuthorized()) {
|
||||
log.debug('MiroTalk get meeting - Unauthorized', {
|
||||
header: req.headers,
|
||||
body: req.body,
|
||||
});
|
||||
return res.status(403).json({ error: 'Unauthorized!' });
|
||||
}
|
||||
// setup meeting URL
|
||||
let meetingURL = api.getMeetingURL();
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(JSON.stringify({ meeting: meetingURL }));
|
||||
// log.debug the output if all done
|
||||
log.debug('MiroTalk get meeting - Authorized', {
|
||||
header: req.headers,
|
||||
body: req.body,
|
||||
meeting: meetingURL,
|
||||
});
|
||||
});
|
||||
|
||||
// ####################################################
|
||||
// NGROK
|
||||
// ####################################################
|
||||
|
||||
async function ngrokStart() {
|
||||
try {
|
||||
await ngrok.authtoken(config.ngrokAuthToken);
|
||||
await ngrok.connect(config.listenPort);
|
||||
let api = ngrok.getApi();
|
||||
let data = await api.listTunnels();
|
||||
let pu0 = data.tunnels[0].public_url;
|
||||
let pu1 = data.tunnels[1].public_url;
|
||||
let tunnel = pu0.startsWith('https') ? pu0 : pu1;
|
||||
log.debug('Listening on', {
|
||||
https: localHost,
|
||||
ngrok: tunnel,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Ngrok Start error: ', err);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// ####################################################
|
||||
// START SERVER
|
||||
// ####################################################
|
||||
|
||||
httpsServer.listen(config.listenPort, () => {
|
||||
if (config.ngrokAuthToken !== '') {
|
||||
ngrokStart();
|
||||
return;
|
||||
}
|
||||
log.debug('Listening on', {
|
||||
https: localHost,
|
||||
});
|
||||
});
|
||||
|
||||
// ####################################################
|
||||
// WORKERS
|
||||
// ####################################################
|
||||
|
||||
(async () => {
|
||||
await createWorkers();
|
||||
})();
|
||||
|
||||
async function createWorkers() {
|
||||
let { numWorkers } = config.mediasoup;
|
||||
|
||||
for (let i = 0; i < numWorkers; i++) {
|
||||
let worker = await mediasoup.createWorker({
|
||||
logLevel: config.mediasoup.worker.logLevel,
|
||||
logTags: config.mediasoup.worker.logTags,
|
||||
rtcMinPort: config.mediasoup.worker.rtcMinPort,
|
||||
rtcMaxPort: config.mediasoup.worker.rtcMaxPort,
|
||||
});
|
||||
worker.on('died', () => {
|
||||
console.error('Mediasoup worker died, exiting in 2 seconds... [pid:%d]', worker.pid);
|
||||
setTimeout(() => process.exit(1), 2000);
|
||||
});
|
||||
workers.push(worker);
|
||||
}
|
||||
}
|
||||
|
||||
async function getMediasoupWorker() {
|
||||
const worker = workers[nextMediasoupWorkerIdx];
|
||||
if (++nextMediasoupWorkerIdx === workers.length) nextMediasoupWorkerIdx = 0;
|
||||
return worker;
|
||||
}
|
||||
|
||||
// ####################################################
|
||||
// SOCKET IO
|
||||
// ####################################################
|
||||
|
||||
io.on('connection', (socket) => {
|
||||
socket.on('createRoom', async ({ room_id }, callback) => {
|
||||
socket.room_id = room_id;
|
||||
|
||||
if (roomList.has(socket.room_id)) {
|
||||
callback('already exists');
|
||||
} else {
|
||||
log.debug('Created room', { room_id: socket.room_id });
|
||||
let worker = await getMediasoupWorker();
|
||||
roomList.set(socket.room_id, new Room(socket.room_id, worker, io));
|
||||
callback(socket.room_id);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('join', (data, cb) => {
|
||||
log.debug('User joined', data);
|
||||
|
||||
if (!roomList.has(socket.room_id)) {
|
||||
return cb({
|
||||
error: 'Room does not exist',
|
||||
});
|
||||
}
|
||||
|
||||
roomList.get(socket.room_id).addPeer(new Peer(socket.id, data));
|
||||
cb(roomList.get(socket.room_id).toJson());
|
||||
});
|
||||
|
||||
socket.on('getRouterRtpCapabilities', (_, callback) => {
|
||||
log.debug('Get RouterRtpCapabilities', getPeerName());
|
||||
try {
|
||||
callback(roomList.get(socket.room_id).getRtpCapabilities());
|
||||
} catch (err) {
|
||||
callback({
|
||||
error: err.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('getProducers', () => {
|
||||
if (!roomList.has(socket.room_id)) return;
|
||||
|
||||
log.debug('Get producers', getPeerName());
|
||||
|
||||
// send all the current producer to newly joined member
|
||||
let producerList = roomList.get(socket.room_id).getProducerListForPeer();
|
||||
|
||||
socket.emit('newProducers', producerList);
|
||||
});
|
||||
|
||||
socket.on('createWebRtcTransport', async (_, callback) => {
|
||||
log.debug('Create webrtc transport', getPeerName());
|
||||
try {
|
||||
const { params } = await roomList.get(socket.room_id).createWebRtcTransport(socket.id);
|
||||
callback(params);
|
||||
} catch (err) {
|
||||
console.error('Create WebRtc Transport error: ', err);
|
||||
callback({
|
||||
error: err.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('connectTransport', async ({ transport_id, dtlsParameters }, callback) => {
|
||||
log.debug('Connect transport', getPeerName());
|
||||
|
||||
if (!roomList.has(socket.room_id)) return;
|
||||
await roomList.get(socket.room_id).connectPeerTransport(socket.id, transport_id, dtlsParameters);
|
||||
|
||||
callback('success');
|
||||
});
|
||||
|
||||
socket.on('produce', async ({ kind, rtpParameters, producerTransportId }, callback) => {
|
||||
if (!roomList.has(socket.room_id)) {
|
||||
return callback({ error: 'Room not found' });
|
||||
}
|
||||
|
||||
let producer_id = await roomList
|
||||
.get(socket.room_id)
|
||||
.produce(socket.id, producerTransportId, rtpParameters, kind);
|
||||
|
||||
log.debug('Produce', {
|
||||
kind: kind,
|
||||
peer_name: getPeerName(false),
|
||||
producer_id: producer_id,
|
||||
});
|
||||
|
||||
callback({
|
||||
producer_id,
|
||||
});
|
||||
});
|
||||
|
||||
socket.on('consume', async ({ consumerTransportId, producerId, rtpCapabilities }, callback) => {
|
||||
if (!roomList.has(socket.room_id)) {
|
||||
return callback({ error: 'Room not found' });
|
||||
}
|
||||
|
||||
let params = await roomList
|
||||
.get(socket.room_id)
|
||||
.consume(socket.id, consumerTransportId, producerId, rtpCapabilities);
|
||||
|
||||
log.debug('Consuming', {
|
||||
peer_name: getPeerName(false),
|
||||
producer_id: producerId,
|
||||
consumer_id: params.id,
|
||||
});
|
||||
|
||||
callback(params);
|
||||
});
|
||||
|
||||
socket.on('producerClosed', ({ producer_id }) => {
|
||||
log.debug('Producer close', getPeerName());
|
||||
|
||||
roomList.get(socket.room_id).closeProducer(socket.id, producer_id);
|
||||
});
|
||||
|
||||
socket.on('resume', async (_, callback) => {
|
||||
await consumer.resume();
|
||||
callback();
|
||||
});
|
||||
|
||||
socket.on('getMyRoomInfo', (_, cb) => {
|
||||
cb(roomList.get(socket.room_id).toJson());
|
||||
});
|
||||
|
||||
socket.on('message', (data) => {
|
||||
log.debug('message', data);
|
||||
roomList.get(socket.room_id).broadCast(socket.id, 'message', {
|
||||
peer_name: data.peer_name,
|
||||
peer_msg: data.peer_msg,
|
||||
});
|
||||
});
|
||||
|
||||
socket.on('disconnect', () => {
|
||||
if (!socket.room_id) return;
|
||||
|
||||
log.debug('Disconnect', getPeerName());
|
||||
|
||||
roomList.get(socket.room_id).removePeer(socket.id);
|
||||
});
|
||||
|
||||
socket.on('exitRoom', async (_, callback) => {
|
||||
if (!roomList.has(socket.room_id)) {
|
||||
callback({
|
||||
error: 'Not currently in a room',
|
||||
});
|
||||
return;
|
||||
}
|
||||
log.debug('Exit room', getPeerName());
|
||||
|
||||
// close transports
|
||||
await roomList.get(socket.room_id).removePeer(socket.id);
|
||||
if (roomList.get(socket.room_id).getPeers().size === 0) {
|
||||
roomList.delete(socket.room_id);
|
||||
}
|
||||
|
||||
socket.room_id = null;
|
||||
|
||||
callback('Successfully exited room');
|
||||
});
|
||||
|
||||
// common
|
||||
function getPeerName(json = true) {
|
||||
if (json) {
|
||||
return {
|
||||
peer_name:
|
||||
roomList.get(socket.room_id) && roomList.get(socket.room_id).getPeers().get(socket.id).peer_name,
|
||||
};
|
||||
}
|
||||
return roomList.get(socket.room_id) && roomList.get(socket.room_id).getPeers().get(socket.id).peer_name;
|
||||
}
|
||||
});
|
||||
21
src/ServerApi.js
Normal file
21
src/ServerApi.js
Normal file
@@ -0,0 +1,21 @@
|
||||
'use strict';
|
||||
|
||||
const config = require('./config');
|
||||
const { v4: uuidV4 } = require('uuid');
|
||||
|
||||
module.exports = class ServerApi {
|
||||
constructor(host, authorization) {
|
||||
this._host = host;
|
||||
this._authorization = authorization;
|
||||
this._api_key_secret = config.apiKeySecret;
|
||||
}
|
||||
|
||||
isAuthorized() {
|
||||
if (this._authorization != this._api_key_secret) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
getMeetingURL() {
|
||||
return 'https://' + this._host + '/join/' + uuidV4();
|
||||
}
|
||||
};
|
||||
78
src/config.template.js
Normal file
78
src/config.template.js
Normal file
@@ -0,0 +1,78 @@
|
||||
'use strict';
|
||||
|
||||
const os = require('os');
|
||||
const ifaces = os.networkInterfaces();
|
||||
|
||||
const getLocalIp = () => {
|
||||
let localIp = '127.0.0.1';
|
||||
Object.keys(ifaces).forEach((ifname) => {
|
||||
for (const iface of ifaces[ifname]) {
|
||||
// Ignore IPv6 and 127.0.0.1
|
||||
if (iface.family !== 'IPv4' || iface.internal !== false) {
|
||||
continue;
|
||||
}
|
||||
// Set the local ip to the first IPv4 address found and exit the loop
|
||||
localIp = iface.address;
|
||||
return;
|
||||
}
|
||||
});
|
||||
return localIp;
|
||||
};
|
||||
|
||||
// https://api.ipify.org
|
||||
|
||||
module.exports = {
|
||||
listenIp: '0.0.0.0',
|
||||
listenPort: 3010,
|
||||
// ssl/README.md
|
||||
sslCrt: '../ssl/cert.pem',
|
||||
sslKey: '../ssl/key.pem',
|
||||
/*
|
||||
Ngrok
|
||||
1. Goto https://ngrok.com
|
||||
2. Get started for free
|
||||
3. Copy YourNgrokAuthToken: https://dashboard.ngrok.com/get-started/your-authtoken
|
||||
*/
|
||||
ngrokAuthToken: '',
|
||||
apiKeySecret: 'mirotalksfu_default_secret',
|
||||
mediasoup: {
|
||||
// Worker settings
|
||||
numWorkers: Object.keys(os.cpus()).length,
|
||||
worker: {
|
||||
rtcMinPort: 40000,
|
||||
rtcMaxPort: 41000,
|
||||
logLevel: 'warn',
|
||||
logTags: ['info', 'ice', 'dtls', 'rtp', 'srtp', 'rtcp'],
|
||||
},
|
||||
// Router settings
|
||||
router: {
|
||||
mediaCodecs: [
|
||||
{
|
||||
kind: 'audio',
|
||||
mimeType: 'audio/opus',
|
||||
clockRate: 48000,
|
||||
channels: 2,
|
||||
},
|
||||
{
|
||||
kind: 'video',
|
||||
mimeType: 'video/VP8',
|
||||
clockRate: 90000,
|
||||
parameters: {
|
||||
'x-google-start-bitrate': 1000,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// WebRtcTransport settings
|
||||
webRtcTransport: {
|
||||
listenIps: [
|
||||
{
|
||||
ip: '0.0.0.0',
|
||||
announcedIp: getLocalIp(), // replace by public static IP address https://api.ipify.org
|
||||
},
|
||||
],
|
||||
maxIncomingBitrate: 1500000,
|
||||
initialAvailableOutgoingBitrate: 1000000,
|
||||
},
|
||||
},
|
||||
};
|
||||
المرجع في مشكلة جديدة
حظر مستخدم