mediasoup v2 across network not working in nodejs cluster

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.

Use mediasoup v3 in which you can select different listenIp and announcedIp per transport. That’s not possible in v2, sorry.

But this code is working when users are connected to the same wifi network. The problem comes in when one user is connected to wifi another mobile data, this is not working. Any ideas or workarounds in V2 itself, else should I move my code towards v3 with no other option. the socket connection is working fine but mediasoup client v2 gives timeout error when one user is connected to different network.

I’m afraid I don’t understand what you mean with “one user is connected to wifi another mobile data, this is not working”.

  • mediasoup is deployed in a server, right?
  • Clients connect from their local Wi-Fi or mobile data, right?

yes server is deployed and clients could connect to app through wifi and mobile data. When my app is running in single instance and media server runs on eigth cores it is woking fine.

That is users from all networks could connect and receive streams.

But when i cluster my app through socket io redis and include mediaserver there the above mentioned problem occurs. Users from same network could receive streams but user across different networks are not receiving their streams.

Ill upload a simple version of the problem in a while.

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')(server);

module.exports = app; ```

socket io mediasoup sever 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);

There is obviously an architecture problem in your backend. Check carefully what you are doing and be aware of the implications. I don’t know how your “cluster” is, but all participants into the same room should connect to the SAME mediasoup server instance.

However, we cannot give support about devops stuff. That’s not the purpose of this forum.

I want to make sure that this is a devops problem. Ok. Let me look into that. I think the problem with clustering is it creates multiple server instances which fail to establish connection. thanks for your response.

I resolved this problem. Thanks for pointing it out. The thing is they are not connecting to the same media server instance. Declaring it global solved the problem for me. Thank you once again. I will mark this as solution