You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

678 lines
16 KiB

6 months ago
const os = require('os');
const path = require('path');
const repl = require('repl');
const readline = require('readline');
const net = require('net');
const fs = require('fs');
const mediasoup = require('mediasoup');
const colors = require('colors/safe');
const pidusage = require('pidusage');
const SOCKET_PATH_UNIX = '/tmp/mediasoup-demo.sock';
const SOCKET_PATH_WIN = path.join('\\\\?\\pipe', process.cwd(), 'mediasoup-demo');
const SOCKET_PATH = os.platform() === 'win32' ? SOCKET_PATH_WIN : SOCKET_PATH_UNIX;
// Maps to store all mediasoup objects.
const workers = new Map();
const webRtcServers = new Map();
const routers = new Map();
const transports = new Map();
const producers = new Map();
const consumers = new Map();
const dataProducers = new Map();
const dataConsumers = new Map();
class Interactive
{
constructor(socket)
{
this._socket = socket;
this._isTerminalOpen = false;
}
openCommandConsole()
{
this.log('\n[opening Readline Command Console...]');
this.log('type help to print available commands');
const cmd = readline.createInterface(
{
input : this._socket,
output : this._socket,
terminal : true
});
cmd.on('close', () =>
{
if (this._isTerminalOpen)
return;
this.log('\nexiting...');
this._socket.end();
});
const readStdin = () =>
{
cmd.question('cmd> ', async (input) =>
{
const params = input.split(/[\s\t]+/);
const command = params.shift();
switch (command)
{
case '':
{
readStdin();
break;
}
case 'h':
case 'help':
{
this.log('');
this.log('available commands:');
this.log('- h, help : show this message');
this.log('- usage : show CPU and memory usage of the Node.js and mediasoup-worker processes');
this.log('- logLevel level : changes logLevel in all mediasoup Workers');
this.log('- logTags [tag] [tag] : changes logTags in all mediasoup Workers (values separated by space)');
this.log('- dw, dumpWorkers : dump mediasoup Workers');
this.log('- dwrs, dumpWebRtcServer [id] : dump mediasoup WebRtcServer with given id (or the latest created one)');
this.log('- dr, dumpRouter [id] : dump mediasoup Router with given id (or the latest created one)');
this.log('- dt, dumpTransport [id] : dump mediasoup Transport with given id (or the latest created one)');
this.log('- dp, dumpProducer [id] : dump mediasoup Producer with given id (or the latest created one)');
this.log('- dc, dumpConsumer [id] : dump mediasoup Consumer with given id (or the latest created one)');
this.log('- ddp, dumpDataProducer [id] : dump mediasoup DataProducer with given id (or the latest created one)');
this.log('- ddc, dumpDataConsumer [id] : dump mediasoup DataConsumer with given id (or the latest created one)');
this.log('- st, statsTransport [id] : get stats for mediasoup Transport with given id (or the latest created one)');
this.log('- sp, statsProducer [id] : get stats for mediasoup Producer with given id (or the latest created one)');
this.log('- sc, statsConsumer [id] : get stats for mediasoup Consumer with given id (or the latest created one)');
this.log('- sdp, statsDataProducer [id] : get stats for mediasoup DataProducer with given id (or the latest created one)');
this.log('- sdc, statsDataConsumer [id] : get stats for mediasoup DataConsumer with given id (or the latest created one)');
this.log('- t, terminal : open Node REPL Terminal');
this.log('');
readStdin();
break;
}
case 'u':
case 'usage':
{
let usage = await pidusage(process.pid);
this.log(`Node.js process [pid:${process.pid}]:\n${JSON.stringify(usage, null, ' ')}`);
for (const worker of workers.values())
{
usage = await pidusage(worker.pid);
this.log(`mediasoup-worker process [pid:${worker.pid}]:\n${JSON.stringify(usage, null, ' ')}`);
}
break;
}
case 'logLevel':
{
const level = params[0];
const promises = [];
for (const worker of workers.values())
{
promises.push(worker.updateSettings({ logLevel: level }));
}
try
{
await Promise.all(promises);
this.log('done');
}
catch (error)
{
this.error(String(error));
}
break;
}
case 'logTags':
{
const tags = params;
const promises = [];
for (const worker of workers.values())
{
promises.push(worker.updateSettings({ logTags: tags }));
}
try
{
await Promise.all(promises);
this.log('done');
}
catch (error)
{
this.error(String(error));
}
break;
}
case 'dw':
case 'dumpWorkers':
{
for (const worker of workers.values())
{
try
{
const dump = await worker.dump();
this.log(`worker.dump():\n${JSON.stringify(dump, null, ' ')}`);
}
catch (error)
{
this.error(`worker.dump() failed: ${error}`);
}
}
break;
}
case 'dwrs':
case 'dumpWebRtcServer':
{
const id = params[0] || Array.from(webRtcServers.keys()).pop();
const webRtcServer = webRtcServers.get(id);
if (!webRtcServer)
{
this.error('WebRtcServer not found');
break;
}
try
{
const dump = await webRtcServer.dump();
this.log(`webRtcServer.dump():\n${JSON.stringify(dump, null, ' ')}`);
}
catch (error)
{
this.error(`webRtcServer.dump() failed: ${error}`);
}
break;
}
case 'dr':
case 'dumpRouter':
{
const id = params[0] || Array.from(routers.keys()).pop();
const router = routers.get(id);
if (!router)
{
this.error('Router not found');
break;
}
try
{
const dump = await router.dump();
this.log(`router.dump():\n${JSON.stringify(dump, null, ' ')}`);
}
catch (error)
{
this.error(`router.dump() failed: ${error}`);
}
break;
}
case 'dt':
case 'dumpTransport':
{
const id = params[0] || Array.from(transports.keys()).pop();
const transport = transports.get(id);
if (!transport)
{
this.error('Transport not found');
break;
}
try
{
const dump = await transport.dump();
this.log(`transport.dump():\n${JSON.stringify(dump, null, ' ')}`);
}
catch (error)
{
this.error(`transport.dump() failed: ${error}`);
}
break;
}
case 'dp':
case 'dumpProducer':
{
const id = params[0] || Array.from(producers.keys()).pop();
const producer = producers.get(id);
if (!producer)
{
this.error('Producer not found');
break;
}
try
{
const dump = await producer.dump();
this.log(`producer.dump():\n${JSON.stringify(dump, null, ' ')}`);
}
catch (error)
{
this.error(`producer.dump() failed: ${error}`);
}
break;
}
case 'dc':
case 'dumpConsumer':
{
const id = params[0] || Array.from(consumers.keys()).pop();
const consumer = consumers.get(id);
if (!consumer)
{
this.error('Consumer not found');
break;
}
try
{
const dump = await consumer.dump();
this.log(`consumer.dump():\n${JSON.stringify(dump, null, ' ')}`);
}
catch (error)
{
this.error(`consumer.dump() failed: ${error}`);
}
break;
}
case 'ddp':
case 'dumpDataProducer':
{
const id = params[0] || Array.from(dataProducers.keys()).pop();
const dataProducer = dataProducers.get(id);
if (!dataProducer)
{
this.error('DataProducer not found');
break;
}
try
{
const dump = await dataProducer.dump();
this.log(`dataProducer.dump():\n${JSON.stringify(dump, null, ' ')}`);
}
catch (error)
{
this.error(`dataProducer.dump() failed: ${error}`);
}
break;
}
case 'ddc':
case 'dumpDataConsumer':
{
const id = params[0] || Array.from(dataConsumers.keys()).pop();
const dataConsumer = dataConsumers.get(id);
if (!dataConsumer)
{
this.error('DataConsumer not found');
break;
}
try
{
const dump = await dataConsumer.dump();
this.log(`dataConsumer.dump():\n${JSON.stringify(dump, null, ' ')}`);
}
catch (error)
{
this.error(`dataConsumer.dump() failed: ${error}`);
}
break;
}
case 'st':
case 'statsTransport':
{
const id = params[0] || Array.from(transports.keys()).pop();
const transport = transports.get(id);
if (!transport)
{
this.error('Transport not found');
break;
}
try
{
const stats = await transport.getStats();
this.log(`transport.getStats():\n${JSON.stringify(stats, null, ' ')}`);
}
catch (error)
{
this.error(`transport.getStats() failed: ${error}`);
}
break;
}
case 'sp':
case 'statsProducer':
{
const id = params[0] || Array.from(producers.keys()).pop();
const producer = producers.get(id);
if (!producer)
{
this.error('Producer not found');
break;
}
try
{
const stats = await producer.getStats();
this.log(`producer.getStats():\n${JSON.stringify(stats, null, ' ')}`);
}
catch (error)
{
this.error(`producer.getStats() failed: ${error}`);
}
break;
}
case 'sc':
case 'statsConsumer':
{
const id = params[0] || Array.from(consumers.keys()).pop();
const consumer = consumers.get(id);
if (!consumer)
{
this.error('Consumer not found');
break;
}
try
{
const stats = await consumer.getStats();
this.log(`consumer.getStats():\n${JSON.stringify(stats, null, ' ')}`);
}
catch (error)
{
this.error(`consumer.getStats() failed: ${error}`);
}
break;
}
case 'sdp':
case 'statsDataProducer':
{
const id = params[0] || Array.from(dataProducers.keys()).pop();
const dataProducer = dataProducers.get(id);
if (!dataProducer)
{
this.error('DataProducer not found');
break;
}
try
{
const stats = await dataProducer.getStats();
this.log(`dataProducer.getStats():\n${JSON.stringify(stats, null, ' ')}`);
}
catch (error)
{
this.error(`dataProducer.getStats() failed: ${error}`);
}
break;
}
case 'sdc':
case 'statsDataConsumer':
{
const id = params[0] || Array.from(dataConsumers.keys()).pop();
const dataConsumer = dataConsumers.get(id);
if (!dataConsumer)
{
this.error('DataConsumer not found');
break;
}
try
{
const stats = await dataConsumer.getStats();
this.log(`dataConsumer.getStats():\n${JSON.stringify(stats, null, ' ')}`);
}
catch (error)
{
this.error(`dataConsumer.getStats() failed: ${error}`);
}
break;
}
case 't':
case 'terminal':
{
this._isTerminalOpen = true;
cmd.close();
this.openTerminal();
return;
}
default:
{
this.error(`unknown command '${command}'`);
this.log('press \'h\' or \'help\' to get the list of available commands');
}
}
readStdin();
});
};
readStdin();
}
openTerminal()
{
this.log('\n[opening Node REPL Terminal...]');
this.log('here you have access to workers, webRtcServers, routers, transports, producers, consumers, dataProducers and dataConsumers ES6 maps');
const terminal = repl.start(
{
input : this._socket,
output : this._socket,
terminal : true,
prompt : 'terminal> ',
useColors : true,
useGlobal : true,
ignoreUndefined : false,
preview : false
});
this._isTerminalOpen = true;
terminal.on('exit', () =>
{
this.log('\n[exiting Node REPL Terminal...]');
this._isTerminalOpen = false;
this.openCommandConsole();
});
}
log(msg)
{
this._socket.write(`${colors.green(msg)}\n`);
}
error(msg)
{
this._socket.write(`${colors.red.bold('ERROR: ')}${colors.red(msg)}\n`);
}
}
function runMediasoupObserver()
{
mediasoup.observer.on('newworker', (worker) =>
{
// Store the latest worker in a global variable.
global.worker = worker;
workers.set(worker.pid, worker);
worker.observer.on('close', () => workers.delete(worker.pid));
worker.observer.on('newwebrtcserver', (webRtcServer) =>
{
// Store the latest webRtcServer in a global variable.
global.webRtcServer = webRtcServer;
webRtcServers.set(webRtcServer.id, webRtcServer);
webRtcServer.observer.on('close', () => webRtcServers.delete(webRtcServer.id));
});
worker.observer.on('newrouter', (router) =>
{
// Store the latest router in a global variable.
global.router = router;
routers.set(router.id, router);
router.observer.on('close', () => routers.delete(router.id));
router.observer.on('newtransport', (transport) =>
{
// Store the latest transport in a global variable.
global.transport = transport;
transports.set(transport.id, transport);
transport.observer.on('close', () => transports.delete(transport.id));
transport.observer.on('newproducer', (producer) =>
{
// Store the latest producer in a global variable.
global.producer = producer;
producers.set(producer.id, producer);
producer.observer.on('close', () => producers.delete(producer.id));
});
transport.observer.on('newconsumer', (consumer) =>
{
// Store the latest consumer in a global variable.
global.consumer = consumer;
consumers.set(consumer.id, consumer);
consumer.observer.on('close', () => consumers.delete(consumer.id));
});
transport.observer.on('newdataproducer', (dataProducer) =>
{
// Store the latest dataProducer in a global variable.
global.dataProducer = dataProducer;
dataProducers.set(dataProducer.id, dataProducer);
dataProducer.observer.on('close', () => dataProducers.delete(dataProducer.id));
});
transport.observer.on('newdataconsumer', (dataConsumer) =>
{
// Store the latest dataConsumer in a global variable.
global.dataConsumer = dataConsumer;
dataConsumers.set(dataConsumer.id, dataConsumer);
dataConsumer.observer.on('close', () => dataConsumers.delete(dataConsumer.id));
});
});
});
});
}
module.exports = async function()
{
// Run the mediasoup observer API.
runMediasoupObserver();
// Make maps global so they can be used during the REPL terminal.
global.workers = workers;
global.routers = routers;
global.transports = transports;
global.producers = producers;
global.consumers = consumers;
global.dataProducers = dataProducers;
global.dataConsumers = dataConsumers;
const server = net.createServer((socket) =>
{
const interactive = new Interactive(socket);
interactive.openCommandConsole();
});
await new Promise((resolve) =>
{
try { fs.unlinkSync(SOCKET_PATH); }
catch (error) {}
server.listen(SOCKET_PATH, resolve);
});
};