Make demo public

This commit is contained in:
Iñaki Baz Castillo
2017-04-23 14:54:30 +02:00
commit f1658f1b3c
54 changed files with 5241 additions and 0 deletions
@@ -0,0 +1,20 @@
-----BEGIN CERTIFICATE-----
MIIDTTCCAjWgAwIBAgIEWPyBpzANBgkqhkiG9w0BAQUFADBQMSEwHwYDVQQDDBht
ZWRpYXNvdXAtZGVtby5sb2NhbGhvc3QxFzAVBgNVBAoMDm1lZGlhc291cC1kZW1v
MRIwEAYDVQQLDAltZWRpYXNvdXAwHhcNMTcwNDIyMTAyNzU1WhcNMzcwNDE4MTAy
NzU1WjBQMSEwHwYDVQQDDBhtZWRpYXNvdXAtZGVtby5sb2NhbGhvc3QxFzAVBgNV
BAoMDm1lZGlhc291cC1kZW1vMRIwEAYDVQQLDAltZWRpYXNvdXAwggEiMA0GCSqG
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCynyO1szmonG3dk+SSQflM5DqzBNTI8ufA
9Za8ltCmq211y6N9cjhKexHS/Eiu8x907QFNgzcagVgrPAA7XJJdckoupKsf4Qrk
wWrpW7s/nJV2H04oIShAdWWbVckRhMLzdz+VWV0rM4AtjBxYu89B3OH9C1p4uYGH
3i4/E147gmk+NaYdddUhYbKYTBhjtjrC2IN/lHT+VfGX8yJ0q0J9Pv6B+17pYJ1P
QAyGhgzmvvi500t1Ke42EI7QOYAGzOw7S/zNl7lBVmXdQGmpGipD7sMVg56txNmt
7RRETaaQ5uNpCxkBcJdIX/DzGV9xNKFoMLm1GUEdTY1RnM7jN0HNAgMBAAGjLzAt
MAwGA1UdEwQFMAMBAf8wHQYDVR0OBBYEFDazfBdo/7PNIfarcfMY6txrxQgoMA0G
CSqGSIb3DQEBBQUAA4IBAQCtqv4Wsnp658HxYyDBxX6CnFpnNfMDqeE8scFeihmX
X3AtJwWMWZJpOX26eOlVqee1h3QyTmvnITau+1Sphttt6EYoHBBHC5It4sCV/kwm
6iiKKah0uxlXUyoj0ylRMwBA16b922OXm8ozDzo3FQWASLstYaUQf1kJtLQimGrH
a4YYiQtRkCO7NvGjaHS8zwmkUdOy8mE1sXol8CiiwCJPGF5vUQMQzj1zqOhQEPLM
44XCmM1CawTfFLhwmgZpPPzYCDMfEz1tF5M/ODOtSTytGoa0H2q4YpXVCiftAQV5
fpSOlyqYaVk7oBkrHS6I6n58MATfuKcPn5YMJ8S/64u1
-----END CERTIFICATE-----
@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAsp8jtbM5qJxt3ZPkkkH5TOQ6swTUyPLnwPWWvJbQpqttdcuj
fXI4SnsR0vxIrvMfdO0BTYM3GoFYKzwAO1ySXXJKLqSrH+EK5MFq6Vu7P5yVdh9O
KCEoQHVlm1XJEYTC83c/lVldKzOALYwcWLvPQdzh/QtaeLmBh94uPxNeO4JpPjWm
HXXVIWGymEwYY7Y6wtiDf5R0/lXxl/MidKtCfT7+gfte6WCdT0AMhoYM5r74udNL
dSnuNhCO0DmABszsO0v8zZe5QVZl3UBpqRoqQ+7DFYOercTZre0URE2mkObjaQsZ
AXCXSF/w8xlfcTShaDC5tRlBHU2NUZzO4zdBzQIDAQABAoIBABLK2WfxbjyGEK0C
NUcJ99+WF3LkLDrkC2vqqqw2tccDPCXrgczd6nwzjIGFF2SIoaOcl8l+55o7R3ps
+p1ENQXt004q9vIIrCu7CbN5ei7MG5Fs470nF+QINeNs2BWmwRf6UM82sq2r4m1o
U0cmozyLr57+xcrzwWP5BSaPtBdQiPjzML7E4PIg9WHbUhYjtgc90a0ruHhp9rlR
QbFsxX9KMcTeJN+pQA+dJWlJrP4EuQurIyupl2zx+XLBzb9j4pL9wlklu3IFI6v+
k+FVTVKXqjndanCbeOvPTli01ILng5UNUEsleWbuvFLuiXlSkduhDVBWECmNxJIR
VCP46EECgYEA6HYNBSyb1NtxXx0Pv2Itg4TbRSP1vQOoQjJzBjrE6FYQJf6lOgMn
sQJFGmZMXiKcyG729Ntw3gvFjnk25V4DNsK1mbLy9cBO8ETMpm2NfAt9QnH40rmp
nd9cgu6v3AFvlBwNJAGsGRFDgshExeQlY28aQy5FevsHE/wc3UpDfRECgYEAxLVw
ocJX/PfvhwW8S0neGtJ4n8h3MLczOxAbHulG44aTwijSIBRxS1E0h+w0jG8Lwr/O
518RpevKhcoGQtf0XuRu1TP2UAtF/rSflCg8a/zHUBen5N2loOWc7Pd9S71klDoi
en7d1NIUZq4Cljb1D1UYW9Ek6wQ9tQFe5EtaKP0CgYAB+N5raNF5oNL5Z5m2mfKg
5wOlNoTjMaC/zwXCy8TX48MHT32/XD999PL5Il0Lf2etG6Pkt+fhOmBWsRiSIZYN
ZOF9iFMfWp5Q04SY9Nz6bG6HncfqocCaokZ6pePADhMQQpyp7Ym0PL1B4skSlLjs
ewjSARZ90JtixATKq9KewQKBgB1T294SJqItqQWdgkxLUBT5qkhQUAzwU3AL369F
Im+Lwf3hripgQd/z1HwraE5DxCIeDNAMKYpuVDyMOVC/98wqDKg23hNjCuWFsoEZ
WqDTCDhVvo9tyGLruPDPmVuweg1reXZ/8bzoMWh5qyMQQIsvqbkOvo1XjYeuE6K/
5UpVAoGBAIvYtZi1a2UFhJmKNaa0dnOWhLAtWjsOh7+k/nM36Zgl1/W7veWD3yiA
HTbyFYK0Rq696OAlVemEVNVEm4bgS2reEJHWYwrQBc07iYVww+qWkocKUVfNmuvd
BUx/QnIKZAhFpDFYLoLUddnxOsLJd4CiuXeVEaLsLZ+eZcIlzWPc
-----END RSA PRIVATE KEY-----
+66
View File
@@ -0,0 +1,66 @@
module.exports =
{
// DEBUG env variable For the NPM debug module.
debug : '*LOG* *WARN* *ERROR* *mediasoup-worker*',
// Listening hostname for `gulp live|open`.
domain : 'localhost',
tls :
{
cert : `${__dirname}/certs/mediasoup-demo.localhost.cert.pem`,
key : `${__dirname}/certs/mediasoup-demo.localhost.key.pem`
},
protoo :
{
listenIp : '0.0.0.0',
listenPort : 3443
},
mediasoup :
{
// mediasoup Server settings.
logLevel : 'debug',
logTags :
[
'info',
// 'ice',
// 'dlts',
'rtp',
// 'srtp',
'rtcp',
// 'rbe',
'rtx'
],
rtcIPv4 : true,
rtcIPv6 : true,
rtcAnnouncedIPv4 : null,
rtcAnnouncedIPv6 : null,
rtcMinPort : 40000,
rtcMaxPort : 49999,
// mediasoup Room settings.
roomCodecs :
[
{
kind : 'audio',
name : 'audio/opus',
clockRate : 48000,
parameters :
{
useInbandFec : 1,
minptime : 10
}
},
{
kind : 'video',
name : 'video/vp8',
clockRate : 90000
}
],
// mediasoup per Peer Transport settings.
peerTransport :
{
udp : true,
tcp : true
},
// mediasoup per Peer max sending bitrate (in kpbs).
maxBitrate : 500000
}
};
+81
View File
@@ -0,0 +1,81 @@
'use strict';
/**
* Tasks:
*
* gulp lint
* Checks source code
*
* gulp watch
* Observes changes in the code
*
* gulp
* Invokes both `lint` and `watch` tasks
*/
const gulp = require('gulp');
const plumber = require('gulp-plumber');
const eslint = require('gulp-eslint');
gulp.task('lint', () =>
{
let src =
[
'gulpfile.js',
'server.js',
'config.example.js',
'config.js',
'lib/**/*.js'
];
return gulp.src(src)
.pipe(plumber())
.pipe(eslint(
{
extends : [ 'eslint:recommended' ],
parserOptions :
{
ecmaVersion : 6,
sourceType : 'module',
ecmaFeatures :
{
impliedStrict : true
}
},
envs :
[
'es6',
'node',
'commonjs'
],
'rules' :
{
'no-console' : 0,
'no-undef' : 2,
'no-unused-vars' : [ 2, { vars: 'all', args: 'after-used' }],
'no-empty' : 0,
'quotes' : [ 2, 'single', { avoidEscape: true } ],
'semi' : [ 2, 'always' ],
'no-multi-spaces' : 0,
'no-whitespace-before-property' : 2,
'space-before-blocks' : 2,
'space-before-function-paren' : [ 2, 'never' ],
'space-in-parens' : [ 2, 'never' ],
'spaced-comment' : [ 2, 'always' ],
}
}))
.pipe(eslint.format());
});
gulp.task('watch', (done) =>
{
let src = [ 'gulpfile.js', 'server.js', 'config.js', 'lib/**/*.js' ];
gulp.watch(src, gulp.series(
'lint'
));
done();
});
gulp.task('default', gulp.series('lint', 'watch'));
+498
View File
@@ -0,0 +1,498 @@
'use strict';
const EventEmitter = require('events').EventEmitter;
const protooServer = require('protoo-server');
const webrtc = require('mediasoup').webrtc;
const logger = require('./logger')('Room');
const config = require('../config');
const MAX_BITRATE = config.mediasoup.maxBitrate || 3000000;
const MIN_BITRATE = Math.min(50000 || MAX_BITRATE);
const BITRATE_FACTOR = 0.75;
class Room extends EventEmitter
{
constructor(roomId, mediaServer)
{
logger.log('constructor() [roomId:"%s"]', roomId);
super();
this.setMaxListeners(Infinity);
// Room ID.
this._roomId = roomId;
// Protoo Room instance.
this._protooRoom = new protooServer.Room();
// mediasoup Room instance.
this._mediaRoom = null;
// Pending peers (this is because at the time we get the first peer, the
// mediasoup room does not yet exist).
this._pendingProtooPeers = [];
// Current max bitrate for all the participants.
this._maxBitrate = MAX_BITRATE;
// Create a mediasoup room.
mediaServer.createRoom(
{
mediaCodecs : config.mediasoup.roomCodecs
})
.then((room) =>
{
logger.debug('mediasoup room created');
this._mediaRoom = room;
process.nextTick(() =>
{
this._mediaRoom.on('newpeer', (peer) =>
{
this._updateMaxBitrate();
peer.on('close', () =>
{
this._updateMaxBitrate();
});
});
});
// Run all the pending join requests.
for (let protooPeer of this._pendingProtooPeers)
{
this._handleProtooPeer(protooPeer);
}
});
}
get id()
{
return this._roomId;
}
close()
{
logger.debug('close()');
// Close the protoo Room.
this._protooRoom.close();
// Close the mediasoup Room.
if (this._mediaRoom)
this._mediaRoom.close();
// Emit 'close' event.
this.emit('close');
}
logStatus()
{
if (!this._mediaRoom)
return;
logger.log(
'logStatus() [room id:"%s", protoo peers:%s, mediasoup peers:%s]',
this._roomId,
this._protooRoom.peers.length,
this._mediaRoom.peers.length);
}
createProtooPeer(peerId, transport)
{
logger.log('createProtooPeer() [peerId:"%s"]', peerId);
if (this._protooRoom.hasPeer(peerId))
{
logger.warn('createProtooPeer() | there is already a peer with same peerId, closing the previous one [peerId:"%s"]', peerId);
let protooPeer = this._protooRoom.getPeer(peerId);
protooPeer.close();
}
return this._protooRoom.createPeer(peerId, transport)
.then((protooPeer) =>
{
if (this._mediaRoom)
this._handleProtooPeer(protooPeer);
else
this._pendingProtooPeers.push(protooPeer);
});
}
_handleProtooPeer(protooPeer)
{
logger.debug('_handleProtooPeer() [peerId:"%s"]', protooPeer.id);
let mediaPeer = this._mediaRoom.Peer(protooPeer.id);
let peerconnection;
protooPeer.data.msids = [];
protooPeer.on('close', () =>
{
logger.debug('protoo Peer "close" event [peerId:"%s"]', protooPeer.id);
this._protooRoom.spread(
'removepeer',
{
peer :
{
id : protooPeer.id,
msids : protooPeer.data.msids
}
});
// Close the media stuff.
if (peerconnection)
peerconnection.close();
else
mediaPeer.close();
// If this is the latest peer in the room, close the room.
// However, wait a bit (for reconnections).
setTimeout(() =>
{
if (this._mediaRoom && this._mediaRoom.closed)
return;
if (this._protooRoom.peers.length === 0)
{
logger.log(
'last peer in the room left, closing the room [roomId:"%s"]',
this._roomId);
this.close();
}
}, 10000);
});
Promise.resolve()
// Send 'join' request to the new peer.
.then(() =>
{
return protooPeer.send(
'joinme',
{
peerId : protooPeer.id,
roomId : this.id
});
})
// Create a RTCPeerConnection instance and set media capabilities.
.then((data) =>
{
peerconnection = new webrtc.RTCPeerConnection(
{
peer : mediaPeer,
usePlanB : !!data.usePlanB,
transportOptions : config.mediasoup.peerTransport,
maxBitrate : this._maxBitrate
});
// Store the RTCPeerConnection instance within the protoo Peer.
protooPeer.data.peerconnection = peerconnection;
mediaPeer.on('newtransport', (transport) =>
{
transport.on('iceselectedtuplechange', (data) =>
{
logger.log('"iceselectedtuplechange" event [peerId:"%s", protocol:%s, remoteIP:%s, remotePort:%s]',
protooPeer.id, data.protocol, data.remoteIP, data.remotePort);
});
});
// Set RTCPeerConnection capabilities.
return peerconnection.setCapabilities(data.capabilities);
})
// Send 'peers' request for the new peer to know about the existing peers.
.then(() =>
{
return protooPeer.send(
'peers',
{
peers : this._protooRoom.peers
// Filter this protoo Peer.
.filter((peer) =>
{
return peer !== protooPeer;
})
.map((peer) =>
{
return {
id : peer.id,
msids : peer.data.msids
};
})
});
})
// Tell all the other peers about the new peer.
.then(() =>
{
this._protooRoom.spread(
'addpeer',
{
peer :
{
id : protooPeer.id,
msids : protooPeer.data.msids
}
},
[ protooPeer ]);
})
.then(() =>
{
// Send initial SDP offer.
return this._sendOffer(protooPeer,
{
offerToReceiveAudio : 1,
offerToReceiveVideo : 1
});
})
.then(() =>
{
// Handle PeerConnection events.
peerconnection.on('negotiationneeded', () =>
{
logger.debug('"negotiationneeded" event [peerId:"%s"]', protooPeer.id);
// Send SDP re-offer.
this._sendOffer(protooPeer);
});
peerconnection.on('signalingstatechange', () =>
{
logger.debug('"signalingstatechange" event [peerId:"%s", signalingState:%s]',
protooPeer.id, peerconnection.signalingState);
});
})
.then(() =>
{
protooPeer.on('request', (request, accept, reject) =>
{
logger.debug('protoo Peer "request" event [method:%s]', request.method);
switch(request.method)
{
case 'reofferme':
{
accept();
this._sendOffer(protooPeer);
break;
}
case 'restartice':
{
peerconnection.restartIce()
.then(() =>
{
accept();
})
.catch((error) =>
{
logger.error('"restartice" request failed: %s', error);
logger.error('stack:\n' + error.stack);
reject(500, `"restartice" failed: ${error.message}`);
});
break;
}
case 'disableremotevideo':
{
let videoMsid = request.data.msid;
let disable = request.data.disable;
let videoRtpSender;
for (let rtpSender of mediaPeer.rtpSenders)
{
if (rtpSender.kind !== 'video')
continue;
let msid = rtpSender.rtpParameters.userParameters.msid.split(/\s/)[0];
if (msid === videoMsid)
{
videoRtpSender = rtpSender;
break;
}
}
if (videoRtpSender)
{
return Promise.resolve()
.then(() =>
{
if (disable)
return videoRtpSender.disable();
else
return videoRtpSender.enable();
})
.then(() =>
{
accept();
})
.catch((error) =>
{
logger.error('"disableremotevideo" request failed: %s', error);
logger.error('stack:\n' + error.stack);
reject(500, `"disableremotevideo" failed: ${error.message}`);
});
}
else
{
reject(404, 'msid not found');
}
break;
}
default:
{
logger.error('unknown method');
reject(404, 'unknown method');
}
}
});
})
.catch((error) =>
{
logger.error('_handleProtooPeer() failed: %s', error.message);
logger.error('stack:\n' + error.stack);
protooPeer.close();
});
}
_sendOffer(protooPeer, options)
{
logger.debug('_sendOffer() [peerId:"%s"]', protooPeer.id);
let peerconnection = protooPeer.data.peerconnection;
let mediaPeer = peerconnection.peer;
return Promise.resolve()
.then(() =>
{
return peerconnection.createOffer(options);
})
.then((desc) =>
{
return peerconnection.setLocalDescription(desc);
})
// Send the SDP offer to the peer.
.then(() =>
{
return protooPeer.send(
'offer',
{
offer : peerconnection.localDescription.serialize()
});
})
// Process the SDP answer from the peer.
.then((data) =>
{
let answer = data.answer;
return peerconnection.setRemoteDescription(answer);
})
.then(() =>
{
let oldMsids = protooPeer.data.msids;
// Reset peer's msids.
protooPeer.data.msids = [];
let setMsids = new Set();
// Update peer's msids information.
for (let rtpReceiver of mediaPeer.rtpReceivers)
{
let msid = rtpReceiver.rtpParameters.userParameters.msid.split(/\s/)[0];
setMsids.add(msid);
}
protooPeer.data.msids = Array.from(setMsids);
// If msids changed, notify.
let sameValues = (
oldMsids.length == protooPeer.data.msids.length) &&
oldMsids.every((element, index) =>
{
return element === protooPeer.data.msids[index];
});
if (!sameValues)
{
this._protooRoom.spread(
'updatepeer',
{
peer :
{
id : protooPeer.id,
msids : protooPeer.data.msids
}
},
[ protooPeer ]);
}
})
.catch((error) =>
{
logger.error('_sendOffer() failed: %s', error);
logger.error('stack:\n' + error.stack);
logger.warn('resetting peerconnection');
peerconnection.reset();
});
}
_updateMaxBitrate()
{
if (this._mediaRoom.closed)
return;
let numPeers = this._mediaRoom.peers.length;
let previousMaxBitrate = this._maxBitrate;
let newMaxBitrate;
if (numPeers <= 2)
{
newMaxBitrate = MAX_BITRATE;
}
else
{
newMaxBitrate = Math.round(MAX_BITRATE / ((numPeers - 1) * BITRATE_FACTOR));
if (newMaxBitrate < MIN_BITRATE)
newMaxBitrate = MIN_BITRATE;
}
if (newMaxBitrate === previousMaxBitrate)
return;
for (let peer of this._mediaRoom.peers)
{
if (!peer.capabilities || peer.closed)
continue;
for (let transport of peer.transports)
{
if (transport.closed)
continue;
transport.setMaxBitrate(newMaxBitrate);
}
}
logger.log('_updateMaxBitrate() [num peers:%s, before:%skbps, now:%skbps]',
numPeers,
Math.round(previousMaxBitrate / 1000),
Math.round(newMaxBitrate / 1000));
this._maxBitrate = newMaxBitrate;
}
}
module.exports = Room;
+56
View File
@@ -0,0 +1,56 @@
'use strict';
const debug = require('debug');
const NAMESPACE = 'mediasoup-demo-server';
class Logger
{
constructor(prefix)
{
if (prefix)
{
this._debug = debug(NAMESPACE + ':' + prefix);
this._log = debug(NAMESPACE + ':LOG:' + prefix);
this._warn = debug(NAMESPACE + ':WARN:' + prefix);
this._error = debug(NAMESPACE + ':ERROR:' + prefix);
}
else
{
this._debug = debug(NAMESPACE);
this._log = debug(NAMESPACE + ':LOG');
this._warn = debug(NAMESPACE + ':WARN');
this._error = debug(NAMESPACE + ':ERROR');
}
this._debug.log = console.info.bind(console);
this._log.log = console.info.bind(console);
this._warn.log = console.warn.bind(console);
this._error.log = console.error.bind(console);
}
get debug()
{
return this._debug;
}
get log()
{
return this._log;
}
get warn()
{
return this._warn;
}
get error()
{
return this._error;
}
}
module.exports = function(prefix)
{
return new Logger(prefix);
};
+25
View File
@@ -0,0 +1,25 @@
{
"name": "mediasoup-demo-server",
"version": "1.0.0",
"private": true,
"description": "mediasoup demo server",
"author": "Iñaki Baz Castillo <ibc@aliax.net>",
"license": "All Rights Reserved",
"main": "lib/index.js",
"dependencies": {
"colors": "^1.1.2",
"debug": "^2.6.4",
"express": "^4.15.2",
"mediasoup": "^1.0.1",
"protoo-server": "^1.1.4"
},
"devDependencies": {
"babel-plugin-transform-object-assign": "^6.22.0",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-preset-es2015": "^6.24.1",
"babel-preset-react": "^6.24.1",
"gulp": "git://github.com/gulpjs/gulp.git#4.0",
"gulp-eslint": "^3.0.1",
"gulp-plumber": "^1.1.0"
}
}
+297
View File
@@ -0,0 +1,297 @@
#!/usr/bin/env node
'use strict';
process.title = 'mediasoup-demo-server';
const config = require('./config');
process.env.DEBUG = config.debug || '*LOG* *WARN* *ERROR*';
console.log('- process.env.DEBUG:', process.env.DEBUG);
console.log('- config.mediasoup.logLevel:', config.mediasoup.logLevel);
console.log('- config.mediasoup.logTags:', config.mediasoup.logTags);
const fs = require('fs');
const https = require('https');
const url = require('url');
const protooServer = require('protoo-server');
const mediasoup = require('mediasoup');
const readline = require('readline');
const colors = require('colors/safe');
const repl = require('repl');
const logger = require('./lib/logger')();
const Room = require('./lib/Room');
// Map of Room instances indexed by roomId.
let rooms = new Map();
// mediasoup server.
let mediaServer = mediasoup.Server(
{
numWorkers : 1,
logLevel : config.mediasoup.logLevel,
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
});
global.SERVER = mediaServer;
mediaServer.on('newroom', (room) =>
{
global.ROOM = room;
});
// HTTPS server for the protoo WebSocjet server.
let tls =
{
cert : fs.readFileSync(config.tls.cert),
key : fs.readFileSync(config.tls.key)
};
let httpsServer = https.createServer(tls, (req, res) =>
{
res.writeHead(404, 'Not Here');
res.end();
});
httpsServer.listen(config.protoo.listenPort, config.protoo.listenIp, () =>
{
logger.log('protoo WebSocket server running');
});
// Protoo WebSocket server.
let webSocketServer = new protooServer.WebSocketServer(httpsServer,
{
maxReceivedFrameSize : 960000, // 960 KBytes.
maxReceivedMessageSize : 960000,
fragmentOutgoingMessages : true,
fragmentationThreshold : 960000
});
// Handle connections from clients.
webSocketServer.on('connectionrequest', (info, accept, reject) =>
{
// The client indicates the roomId and peerId in the URL query.
let u = url.parse(info.request.url, true);
let roomId = u.query['room-id'];
let peerId = u.query['peer-id'];
if (!roomId || !peerId)
{
logger.warn('connection request without roomId and/or peerId');
reject(400, 'Connection request without roomId and/or peerId');
return;
}
logger.log('connection request [roomId:"%s", peerId:"%s"]', roomId, peerId);
// If an unknown roomId, create a new Room.
if (!rooms.has(roomId))
{
logger.debug('creating a new Room [roomId:"%s"]', roomId);
let room = new Room(roomId, mediaServer);
let logStatusTimer = setInterval(() =>
{
room.logStatus();
}, 10000);
rooms.set(roomId, room);
room.on('close', () =>
{
rooms.delete(roomId);
clearInterval(logStatusTimer);
});
}
let room = rooms.get(roomId);
let transport = accept();
room.createProtooPeer(peerId, transport)
.catch((error) =>
{
logger.error('error creating a protoo peer: %s', error);
});
});
// Listen for keyboard input.
let cmd;
let terminal;
function openCommandConsole()
{
stdinLog('[opening Readline Command Console...]');
closeCommandConsole();
closeTerminal();
cmd = readline.createInterface(
{
input : process.stdin,
output : process.stdout
});
cmd.on('SIGINT', () =>
{
process.exit();
});
readStdin();
function readStdin()
{
cmd.question('cmd> ', (answer) =>
{
switch (answer)
{
case '':
{
readStdin();
break;
}
case 'h':
case 'help':
{
stdinLog('');
stdinLog('available commands:');
stdinLog('- h, help : show this message');
stdinLog('- sd, serverdump : execute server.dump()');
stdinLog('- rd, roomdump : execute room.dump() for the latest created mediasoup Room');
stdinLog('- t, terminal : open REPL Terminal');
stdinLog('');
readStdin();
break;
}
case 'sd':
case 'serverdump':
{
mediaServer.dump()
.then((data) =>
{
stdinLog(`mediaServer.dump() succeeded:\n${JSON.stringify(data, null, ' ')}`);
readStdin();
})
.catch((error) =>
{
stdinError(`mediaServer.dump() failed: ${error}`);
readStdin();
});
break;
}
case 'rd':
case 'roomdump':
{
if (!global.ROOM)
{
readStdin();
break;
}
global.ROOM.dump()
.then((data) =>
{
stdinLog('global.ROOM.dump() succeeded');
stdinLog(`- peers:\n${JSON.stringify(data.peers, null, ' ')}`);
stdinLog(`- num peers: ${data.peers.length}`);
readStdin();
})
.catch((error) =>
{
stdinError(`global.ROOM.dump() failed: ${error}`);
readStdin();
});
break;
}
case 't':
case 'terminal':
{
openTerminal();
break;
}
default:
{
stdinError(`unknown command: ${answer}`);
stdinLog('press \'h\' or \'help\' to get the list of available commands');
readStdin();
}
}
});
}
}
function openTerminal()
{
stdinLog('[opening REPL Terminal...]');
closeCommandConsole();
closeTerminal();
terminal = repl.start({
prompt : 'terminal> ',
useColors : true,
useGlobal : true,
ignoreUndefined : true
});
terminal.on('exit', () =>
{
process.exit();
});
}
function closeCommandConsole()
{
if (cmd)
{
cmd.close();
cmd = undefined;
}
}
function closeTerminal()
{
if (terminal)
{
terminal.removeAllListeners('exit');
terminal.close();
terminal = undefined;
}
}
openCommandConsole();
// Export openCommandConsole function by typing 'c'.
Object.defineProperty(global, 'c',
{
get : function()
{
openCommandConsole();
}
});
function stdinLog(msg)
{
console.log(colors.green(msg));
}
function stdinError(msg)
{
console.error(colors.red.bold('ERROR: ') + colors.red(msg));
}