I have been using mediasoup v2 and it is working fine when the server is running single instance. When I use nodejs cluster and socket io redis it still works when the presenter and the presentee are in the same network but it fails when they are of different networks.
Mediaasoup v : 2
nodejs : 12
npm ; 6
nodejs cluster file
/**
* Module dependencies.
*/
const config = require('./config/config');
var {app, server} = require('./index');
var debug = require('debug')('myapp:server');
const stickyCluser = require('sticky-cluster');
const cluster = require('cluster');
if (cluster.isMaster) {
console.log('Master init');
}
stickyCluser(callback => {
/**
* Get port from environment and store in Express.
*/
var port = normalizePort(config.port || '4040');
var ip = process.env.IP || 'localhost'
app.set('port', port);
app.set('ip', process.env.IP)
/**
* Listen on provided port, on all network interfaces.
*/
server.on('error', onError);
server.on('listening', onListening);
callback(server);
}, {
concurrency: parseInt(require('os').cpus().length - 1) || 3,
port: normalizePort(config.port || '4040'),
debug: true,
env: function(index) {
return {stickycluster_worker_index: index};
}
});
/**
* Normalize a port into a number, string, or false.
*/
function normalizePort(val) {
var port = parseInt(val, 10);
if (isNaN(port)) {
// named pipe
return val;
}
if (port >= 0) {
// port number
return port;
}
return false;
}
/**
* Event listener for HTTP server "error" event.
*/
function onError(error) {
if (error.syscall !== 'listen') {
throw error;
}
var bind = typeof port === 'string'
? 'Pipe ' + port
: 'Port ' + port;
// handle specific listen errors with friendly messages
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
}
/**
* Event listener for HTTP server "listening" event.
*/
function onListening() {
var addr = server.address();
var bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
debug('Listening on ' + bind);
}
scoket io file
const config = require('../../config/mediasoup.config');
const mediasoup = require('mediasoup');
const rooms = new Map();
let mediaServer = mediasoup.Server({
logLevel: config.mediasoup.logLevel,
numWorkers: 1,
logTags: config.mediasoup.logTags,
rtcIPv4: config.mediasoup.rtcIPv4,
rtcIPv6: config.mediasoup.rtcIPv6,
rtcAnnouncedIPv4: config.mediasoup.rtcAnnouncedIPv4,
rtcAnnouncedIPv6: config.mediasoup.rtcAnnouncedIPv6,
rtcMinPort: config.mediasoup.rtcMinPort,
rtcMaxPort: config.mediasoup.rtcMaxPort
});
console.log('Servers created '+ mediaServer.numWorkers);
module.exports.mediaHandler = (io, socket, connectedUsers) => {
let room = null;
// Used for mediaSoup peer
let mediaPeer = null;
let roomId
let peerName;
let activeSpeakerDetector = null;
let currentActiveSpeaker = null;
socket.on('api/v1/session/start', (data) => {
roomId = data.sessionId;
peerName = data.userId;
if (rooms.has(roomId)) {
room = rooms.get(roomId);
} else {
room = mediaServer.Room(config.mediasoup.mediaCodecs);
rooms.set(roomId, room);
activeSpeakerDetector = room.createActiveSpeakerDetector();
activeSpeakerDetector.on('activespeakerchange', (activePeer) =>
{
if (activePeer)
{
logger.info('new active speaker [peerName:%s]', activePeer.name);
currentActiveSpeaker = activePeer;
const activeVideoProducer = activePeer.producers
.find((producer) => producer.kind === 'video');
for (const peer of room.peers)
{
for (const consumer of peer.consumers)
{
if (consumer.kind !== 'video')
continue;
if (consumer.source === activeVideoProducer)
{
consumer.setPreferredProfile('high');
}
else
{
consumer.setPreferredProfile('low');
}
}
}
}
else
{
logger.info('no active speaker');
currentActiveSpeaker = null;
for (const peer of this._mediaRoom.peers)
{
for (const consumer of peer.consumers)
{
if (consumer.kind !== 'video')
continue;
consumer.setPreferredProfile('low');
}
}
}
socket.emit('api/v1/classroom/active-speaker/change', {_id: activePeer.name});
});
room.on('close', () => {
rooms.delete(roomId);
});
}
socket.on('api/v1/producer/pause', (data) => {
console.log(`Room Id ${data.roomId}, userId is ${data.userId}`)
let room = rooms.get(data.roomId);
let peer = room.getPeerByName(data.userId);
let producer = peer.getProducerById(data.producerId);
producer.pause();
});
socket.on('api/v1/producer/pause/remote/audio', (data) => {
let room = rooms.get(data.roomId);
let peer = room.getPeerByName(data.userId);
peer.producers.forEach(consumer => {
if(consumer.kind == 'audio'){
consumer.pause();
}
})
});
socket.on('api/v1/producer/resume/remote/audio', (data) => {
let room = rooms.get(data.roomId);
let peer = room.getPeerByName(data.userId);
let producer = peer.getProducerById(data.producerId);
peer.producers.forEach(consumer => {
if(consumer.kind == 'audio'){
consumer.resume();
}
})
});
socket.on('api/v1/producer/resume', (data) => {
let room = rooms.get(data.roomId);
let peer = room.getPeerByName(data.userId);
let producer = peer.getProducerById(data.producerId);
producer.resume();
});
socket.on('mediasoup-request', (request, cb) => {
switch (request.method) {
case 'queryRoom':
room.receiveRequest(request)
.then((response) => cb(null, response))
.catch((error) => cb(error.toString()));
break;
case 'join':
room.receiveRequest(request)
.then((response) => {
// Get the newly created mediasoup Peer
mediaPeer = room.getPeerByName(peerName);
handleMediaPeer(mediaPeer);
// Send response back
cb(null, response);
})
.catch((error) => cb(error.toString()));
break;
default:
if (mediaPeer) {
mediaPeer.receiveRequest(request)
.then((response) => cb(null, response))
.catch((error) => cb(error.toString()));
}
}
});
socket.on('mediasoup-notification', (notification) => {
console.debug('Got notification from client peer', notification);
// NOTE: mediasoup-client just sends notifications with target 'peer'
if (!mediaPeer) {
console.error('Cannot handle mediaSoup notification, no mediaSoup Peer');
return;
}
mediaPeer.receiveNotification(notification);
});
// Invokes when connection lost on a client side
socket.on('disconnect', () => {
if (mediaPeer) {
mediaPeer.close();
}
});
/**
* Handles all mediaPeer events
*
* @param mediaPeer
*/
const handleMediaPeer = (mediaPeer) => {
mediaPeer.on('notify', (notification) => {
socket.emit('mediasoup-notification', notification);
});
mediaPeer.on('newtransport', (transport) => {
transport.on('close', (originator, appData) => {
transport.close();
});
});
mediaPeer.on('close', (appData) => {
mediaPeer.close();
})
mediaPeer.on('newproducer', (producer) => {
producer.on('close', (originator) => {
producer.close();
});
});
mediaPeer.on('newconsumer', (consumer) => {
consumer.on('close', (originator) => {
consumer.close();
});
});
// Also handle already existing Consumers.
mediaPeer.consumers.forEach((consumer) => {
consumer.on('close', (originator) => {
});
});
}
});
}
index.js file
const app = require('./config/express');
const config = require('./config/config');
require('./config/mongoose');
require('./config/seeddb');
//require('./controllers/elastic-search/elastic-search.service').initIndex();
const https = require('https');
const fs = require('fs');
// HTTPS server for the protoo WebSocket server.
let httpsOptions =
{
listenIp : '0.0.0.0',
// NOTE: Don't change listenPort (client app assumes 4443).
listenPort : process.env.PROTOO_LISTEN_PORT || 443,
// NOTE: Set your own valid certificate files.
tls :
{
cert : process.env.HTTPS_CERT_FULLCHAIN || `${__dirname}/keys/server.crt`,
key : process.env.HTTPS_CERT_PRIVKEY || `${__dirname}/keys/server.key`
}
}
const tls =
{
cert : fs.readFileSync(httpsOptions.tls.cert),
key : fs.readFileSync(httpsOptions.tls.key)
};
// module.parent check is required to support mocha watch
// src: https://github.com/mochajs/mocha/issues/1912
let httpsServer = https.Server(tls, app);
// const server = httpsServer.listen(config.port, () => {
// console.info(`server started on port ${config.port} (${config.env})`);
// });
//Connect to web socket
require('./controllers/sockets/sockets.controller')(httpsServer);
module.exports = {app: app, server: httpsServer};
Problem:
This solution works for user connecting from the same network.
But when the presenter and the joinee are from different requests the joinee is not able to connect to the room.