v2
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
module.exports =
|
||||
{
|
||||
env:
|
||||
{
|
||||
browser: true,
|
||||
es6: true,
|
||||
node: true
|
||||
},
|
||||
extends:
|
||||
[
|
||||
'eslint:recommended'
|
||||
],
|
||||
settings: {},
|
||||
parserOptions:
|
||||
{
|
||||
ecmaVersion: 6,
|
||||
sourceType: 'module',
|
||||
ecmaFeatures:
|
||||
{
|
||||
impliedStrict: true
|
||||
}
|
||||
},
|
||||
rules:
|
||||
{
|
||||
'array-bracket-spacing': [ 2, 'always',
|
||||
{
|
||||
objectsInArrays: true,
|
||||
arraysInArrays: true
|
||||
}],
|
||||
'arrow-parens': [ 2, 'always' ],
|
||||
'arrow-spacing': 2,
|
||||
'block-spacing': [ 2, 'always' ],
|
||||
'brace-style': [ 2, 'allman', { allowSingleLine: true } ],
|
||||
'camelcase': 2,
|
||||
'comma-dangle': 2,
|
||||
'comma-spacing': [ 2, { before: false, after: true } ],
|
||||
'comma-style': 2,
|
||||
'computed-property-spacing': 2,
|
||||
'constructor-super': 2,
|
||||
'func-call-spacing': 2,
|
||||
'generator-star-spacing': 2,
|
||||
'guard-for-in': 2,
|
||||
'indent': [ 2, 'tab', { 'SwitchCase': 1 } ],
|
||||
'key-spacing': [ 2,
|
||||
{
|
||||
singleLine:
|
||||
{
|
||||
beforeColon: false,
|
||||
afterColon: true
|
||||
},
|
||||
multiLine:
|
||||
{
|
||||
beforeColon: true,
|
||||
afterColon: true,
|
||||
align: 'colon'
|
||||
}
|
||||
}],
|
||||
'keyword-spacing': 2,
|
||||
'linebreak-style': [ 2, 'unix' ],
|
||||
'lines-around-comment': [ 2,
|
||||
{
|
||||
allowBlockStart: true,
|
||||
allowObjectStart: true,
|
||||
beforeBlockComment: true,
|
||||
beforeLineComment: false
|
||||
}],
|
||||
'max-len': [ 2, 90,
|
||||
{
|
||||
tabWidth: 2,
|
||||
comments: 110,
|
||||
ignoreUrls: true,
|
||||
ignoreStrings: true,
|
||||
ignoreTemplateLiterals: true,
|
||||
ignoreRegExpLiterals: true
|
||||
}],
|
||||
'newline-after-var': 2,
|
||||
'newline-before-return': 2,
|
||||
'newline-per-chained-call': 2,
|
||||
'no-alert': 2,
|
||||
'no-caller': 2,
|
||||
'no-case-declarations': 2,
|
||||
'no-catch-shadow': 2,
|
||||
'no-class-assign': 2,
|
||||
'no-confusing-arrow': 2,
|
||||
'no-console': 2,
|
||||
'no-const-assign': 2,
|
||||
'no-debugger': 2,
|
||||
'no-dupe-args': 2,
|
||||
'no-dupe-keys': 2,
|
||||
'no-duplicate-case': 2,
|
||||
'no-div-regex': 2,
|
||||
'no-empty': [ 2, { allowEmptyCatch: true } ],
|
||||
'no-empty-pattern': 2,
|
||||
'no-else-return': 0,
|
||||
'no-eval': 2,
|
||||
'no-extend-native': 2,
|
||||
'no-ex-assign': 2,
|
||||
'no-extra-bind': 2,
|
||||
'no-extra-boolean-cast': 2,
|
||||
'no-extra-label': 2,
|
||||
'no-extra-semi': 2,
|
||||
'no-fallthrough': 2,
|
||||
'no-func-assign': 2,
|
||||
'no-global-assign': 2,
|
||||
'no-implicit-coercion': 2,
|
||||
'no-implicit-globals': 2,
|
||||
'no-inner-declarations': 2,
|
||||
'no-invalid-regexp': 2,
|
||||
'no-invalid-this': 2,
|
||||
'no-irregular-whitespace': 2,
|
||||
'no-lonely-if': 2,
|
||||
'no-mixed-operators': 2,
|
||||
'no-mixed-spaces-and-tabs': 2,
|
||||
'no-multi-spaces': 2,
|
||||
'no-multi-str': 2,
|
||||
'no-multiple-empty-lines': [ 2, { max: 1, maxEOF: 0, maxBOF: 0 } ],
|
||||
'no-native-reassign': 2,
|
||||
'no-negated-in-lhs': 2,
|
||||
'no-new': 2,
|
||||
'no-new-func': 2,
|
||||
'no-new-wrappers': 2,
|
||||
'no-obj-calls': 2,
|
||||
'no-proto': 2,
|
||||
'no-prototype-builtins': 0,
|
||||
'no-redeclare': 2,
|
||||
'no-regex-spaces': 2,
|
||||
'no-restricted-imports': 2,
|
||||
'no-return-assign': 2,
|
||||
'no-self-assign': 2,
|
||||
'no-self-compare': 2,
|
||||
'no-sequences': 2,
|
||||
'no-shadow': 2,
|
||||
'no-shadow-restricted-names': 2,
|
||||
'no-spaced-func': 2,
|
||||
'no-sparse-arrays': 2,
|
||||
'no-this-before-super': 2,
|
||||
'no-throw-literal': 2,
|
||||
'no-undef': 2,
|
||||
'no-unexpected-multiline': 2,
|
||||
'no-unmodified-loop-condition': 2,
|
||||
'no-unreachable': 2,
|
||||
'no-unused-vars': [ 1, { vars: 'all', args: 'after-used' }],
|
||||
'no-use-before-define': [ 2, { functions: false } ],
|
||||
'no-useless-call': 2,
|
||||
'no-useless-computed-key': 2,
|
||||
'no-useless-concat': 2,
|
||||
'no-useless-rename': 2,
|
||||
'no-var': 2,
|
||||
'no-whitespace-before-property': 2,
|
||||
'object-curly-newline': 0,
|
||||
'object-curly-spacing': [ 2, 'always' ],
|
||||
'object-property-newline': [ 2, { allowMultiplePropertiesPerLine: true } ],
|
||||
'prefer-const': 2,
|
||||
'prefer-rest-params': 2,
|
||||
'prefer-spread': 2,
|
||||
'prefer-template': 2,
|
||||
'quotes': [ 2, 'single', { avoidEscape: true } ],
|
||||
'semi': [ 2, 'always' ],
|
||||
'semi-spacing': 2,
|
||||
'space-before-blocks': 2,
|
||||
'space-before-function-paren': [ 2, 'never' ],
|
||||
'space-in-parens': [ 2, 'never' ],
|
||||
'spaced-comment': [ 2, 'always' ],
|
||||
'strict': 0,
|
||||
'valid-typeof': 2,
|
||||
'yoda': 2
|
||||
}
|
||||
};
|
||||
+25
-27
@@ -1,7 +1,7 @@
|
||||
module.exports =
|
||||
{
|
||||
// DEBUG env variable For the NPM debug module.
|
||||
debug : '*LOG* *WARN* *ERROR* *mediasoup-worker*',
|
||||
debug : '*INFO* *WARN* *ERROR* *mediasoup-worker*',
|
||||
// Listening hostname for `gulp live|open`.
|
||||
domain : 'localhost',
|
||||
tls :
|
||||
@@ -9,24 +9,19 @@ module.exports =
|
||||
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 :
|
||||
logLevel : 'warn',
|
||||
logTags :
|
||||
[
|
||||
'info',
|
||||
// 'ice',
|
||||
// 'dlts',
|
||||
'ice',
|
||||
'dlts'
|
||||
'rtp',
|
||||
// 'srtp',
|
||||
'srtp',
|
||||
'rtcp',
|
||||
// 'rbe',
|
||||
'rbe',
|
||||
'rtx'
|
||||
],
|
||||
rtcIPv4 : true,
|
||||
@@ -35,32 +30,35 @@ module.exports =
|
||||
rtcAnnouncedIPv6 : null,
|
||||
rtcMinPort : 40000,
|
||||
rtcMaxPort : 49999,
|
||||
// mediasoup Room settings.
|
||||
roomCodecs :
|
||||
// mediasoup Room codecs.
|
||||
mediaCodecs :
|
||||
[
|
||||
{
|
||||
kind : 'audio',
|
||||
name : 'audio/opus',
|
||||
name : 'opus',
|
||||
clockRate : 48000,
|
||||
channels : 2,
|
||||
parameters :
|
||||
{
|
||||
useInbandFec : 1,
|
||||
minptime : 10
|
||||
useinbandfec : 1
|
||||
}
|
||||
},
|
||||
{
|
||||
kind : 'video',
|
||||
name : 'video/vp8',
|
||||
clockRate : 90000
|
||||
kind : 'video',
|
||||
name : 'VP8',
|
||||
clockRate : 90000
|
||||
}
|
||||
// {
|
||||
// kind : 'video',
|
||||
// name : 'H264',
|
||||
// clockRate : 90000,
|
||||
// parameters :
|
||||
// {
|
||||
// 'packetization-mode' : 1
|
||||
// }
|
||||
// }
|
||||
],
|
||||
// mediasoup per Peer Transport settings.
|
||||
peerTransport :
|
||||
{
|
||||
udp : true,
|
||||
tcp : true
|
||||
},
|
||||
// mediasoup per Peer max sending bitrate (in kpbs).
|
||||
// mediasoup per Peer max sending bitrate (in bps).
|
||||
maxBitrate : 500000
|
||||
}
|
||||
};
|
||||
|
||||
+3
-50
@@ -1,5 +1,3 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Tasks:
|
||||
*
|
||||
@@ -19,63 +17,18 @@ const eslint = require('gulp-eslint');
|
||||
|
||||
gulp.task('lint', () =>
|
||||
{
|
||||
let src =
|
||||
const 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())
|
||||
.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'));
|
||||
gulp.task('default', gulp.series('lint'));
|
||||
|
||||
+395
-421
@@ -2,123 +2,53 @@
|
||||
|
||||
const EventEmitter = require('events').EventEmitter;
|
||||
const protooServer = require('protoo-server');
|
||||
const webrtc = require('mediasoup').webrtc;
|
||||
const logger = require('./logger')('Room');
|
||||
const Logger = require('./Logger');
|
||||
const config = require('../config');
|
||||
|
||||
const MAX_BITRATE = config.mediasoup.maxBitrate || 3000000;
|
||||
const MIN_BITRATE = Math.min(50000 || MAX_BITRATE);
|
||||
const MAX_BITRATE = config.mediasoup.maxBitrate || 1000000;
|
||||
const MIN_BITRATE = Math.min(50000, MAX_BITRATE);
|
||||
const BITRATE_FACTOR = 0.75;
|
||||
const MIN_AUDIO_LEVEL = -50;
|
||||
|
||||
const logger = new Logger('Room');
|
||||
|
||||
class Room extends EventEmitter
|
||||
{
|
||||
constructor(roomId, mediaServer)
|
||||
{
|
||||
logger.log('constructor() [roomId:"%s"]', roomId);
|
||||
logger.info('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 = [];
|
||||
|
||||
// Closed flag.
|
||||
this._closed = false;
|
||||
|
||||
try
|
||||
{
|
||||
// Protoo Room instance.
|
||||
this._protooRoom = new protooServer.Room();
|
||||
|
||||
// mediasoup Room instance.
|
||||
this._mediaRoom = mediaServer.Room(config.mediasoup.mediaCodecs);
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
this.close();
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Current max bitrate for all the participants.
|
||||
this._maxBitrate = MAX_BITRATE;
|
||||
// Current active speaker mediasoup Peer.
|
||||
this._activeSpeaker = null;
|
||||
|
||||
// Create a mediasoup room.
|
||||
mediaServer.createRoom(
|
||||
{
|
||||
mediaCodecs : config.mediasoup.roomCodecs
|
||||
})
|
||||
.then((room) =>
|
||||
{
|
||||
logger.debug('mediasoup room created');
|
||||
// Current active speaker.
|
||||
// @type {mediasoup.Peer}
|
||||
this._currentActiveSpeaker = null;
|
||||
|
||||
this._mediaRoom = room;
|
||||
|
||||
process.nextTick(() =>
|
||||
{
|
||||
this._mediaRoom.on('newpeer', (peer) =>
|
||||
{
|
||||
this._updateMaxBitrate();
|
||||
|
||||
peer.on('close', () =>
|
||||
{
|
||||
this._updateMaxBitrate();
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: FIX?
|
||||
this._mediaRoom.on('audiolevels', (entries) =>
|
||||
{
|
||||
logger.debug('room "audiolevels" event');
|
||||
|
||||
for (let entry of entries)
|
||||
{
|
||||
logger.debug('- [peer name:%s, rtpReceiver.id:%s, audio level:%s]',
|
||||
entry.peer.name, entry.rtpReceiver.id, entry.audioLevel);
|
||||
}
|
||||
|
||||
let activeSpeaker;
|
||||
let activeLevel;
|
||||
|
||||
if (entries.length > 0)
|
||||
{
|
||||
activeSpeaker = entries[0].peer;
|
||||
activeLevel = entries[0].audioLevel;
|
||||
|
||||
if (activeLevel < MIN_AUDIO_LEVEL)
|
||||
{
|
||||
activeSpeaker = null;
|
||||
activeLevel = undefined;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
activeSpeaker = null;
|
||||
}
|
||||
|
||||
if (this._activeSpeaker !== activeSpeaker)
|
||||
{
|
||||
let data = {};
|
||||
|
||||
if (activeSpeaker)
|
||||
{
|
||||
logger.debug('active speaker [peer:"%s", volume:%s]',
|
||||
activeSpeaker.name, activeLevel);
|
||||
|
||||
data.peer = { id: activeSpeaker.name };
|
||||
data.level = activeLevel;
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.debug('no current speaker');
|
||||
|
||||
data.peer = null;
|
||||
}
|
||||
|
||||
this._protooRoom.spread('activespeaker', data);
|
||||
}
|
||||
|
||||
this._activeSpeaker = activeSpeaker;
|
||||
});
|
||||
});
|
||||
|
||||
// Run all the pending join requests.
|
||||
for (let protooPeer of this._pendingProtooPeers)
|
||||
{
|
||||
this._handleProtooPeer(protooPeer);
|
||||
}
|
||||
});
|
||||
this._handleMediaRoom();
|
||||
}
|
||||
|
||||
get id()
|
||||
@@ -130,8 +60,11 @@ class Room extends EventEmitter
|
||||
{
|
||||
logger.debug('close()');
|
||||
|
||||
this._closed = true;
|
||||
|
||||
// Close the protoo Room.
|
||||
this._protooRoom.close();
|
||||
if (this._protooRoom)
|
||||
this._protooRoom.close();
|
||||
|
||||
// Close the mediasoup Room.
|
||||
if (this._mediaRoom)
|
||||
@@ -146,367 +79,409 @@ class Room extends EventEmitter
|
||||
if (!this._mediaRoom)
|
||||
return;
|
||||
|
||||
logger.log(
|
||||
logger.info(
|
||||
'logStatus() [room id:"%s", protoo peers:%s, mediasoup peers:%s]',
|
||||
this._roomId,
|
||||
this._protooRoom.peers.length,
|
||||
this._mediaRoom.peers.length);
|
||||
}
|
||||
|
||||
createProtooPeer(peerId, transport)
|
||||
handleConnection(peerName, transport)
|
||||
{
|
||||
logger.log('createProtooPeer() [peerId:"%s"]', peerId);
|
||||
logger.info('handleConnection() [peerName:"%s"]', peerName);
|
||||
|
||||
if (this._protooRoom.hasPeer(peerId))
|
||||
if (this._protooRoom.hasPeer(peerName))
|
||||
{
|
||||
logger.warn('createProtooPeer() | there is already a peer with same peerId, closing the previous one [peerId:"%s"]', peerId);
|
||||
logger.warn(
|
||||
'handleConnection() | there is already a peer with same peerName, ' +
|
||||
'closing the previous one [peerName:"%s"]',
|
||||
peerName);
|
||||
|
||||
let protooPeer = this._protooRoom.getPeer(peerId);
|
||||
const protooPeer = this._protooRoom.getPeer(peerName);
|
||||
|
||||
protooPeer.close();
|
||||
}
|
||||
|
||||
return this._protooRoom.createPeer(peerId, transport)
|
||||
.then((protooPeer) =>
|
||||
const protooPeer = this._protooRoom.createPeer(peerName, transport);
|
||||
|
||||
this._handleProtooPeer(protooPeer);
|
||||
}
|
||||
|
||||
_handleMediaRoom()
|
||||
{
|
||||
logger.debug('_handleMediaRoom()');
|
||||
|
||||
const activeSpeakerDetector = this._mediaRoom.createActiveSpeakerDetector();
|
||||
|
||||
activeSpeakerDetector.on('activespeakerchange', (activePeer) =>
|
||||
{
|
||||
if (activePeer)
|
||||
{
|
||||
if (this._mediaRoom)
|
||||
this._handleProtooPeer(protooPeer);
|
||||
else
|
||||
this._pendingProtooPeers.push(protooPeer);
|
||||
});
|
||||
logger.info('new active speaker [peerName:"%s"]', activePeer.name);
|
||||
|
||||
this._currentActiveSpeaker = activePeer;
|
||||
|
||||
const activeVideoProducer = activePeer.producers
|
||||
.find((producer) => producer.kind === 'video');
|
||||
|
||||
for (const peer of this._mediaRoom.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');
|
||||
|
||||
this._currentActiveSpeaker = null;
|
||||
|
||||
for (const peer of this._mediaRoom.peers)
|
||||
{
|
||||
for (const consumer of peer.consumers)
|
||||
{
|
||||
if (consumer.kind !== 'video')
|
||||
continue;
|
||||
|
||||
consumer.setPreferredProfile('low');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Spread to others via protoo.
|
||||
this._protooRoom.spread(
|
||||
'active-speaker',
|
||||
{
|
||||
peerName : activePeer ? activePeer.name : null
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
_handleProtooPeer(protooPeer)
|
||||
{
|
||||
logger.debug('_handleProtooPeer() [peerId:"%s"]', protooPeer.id);
|
||||
logger.debug('_handleProtooPeer() [peer:"%s"]', protooPeer.id);
|
||||
|
||||
let mediaPeer = this._mediaRoom.Peer(protooPeer.id);
|
||||
let peerconnection;
|
||||
protooPeer.on('request', (request, accept, reject) =>
|
||||
{
|
||||
logger.debug(
|
||||
'protoo "request" event [method:%s, peer:"%s"]',
|
||||
request.method, protooPeer.id);
|
||||
|
||||
protooPeer.data.msids = [];
|
||||
switch (request.method)
|
||||
{
|
||||
case 'mediasoup-request':
|
||||
{
|
||||
const mediasoupRequest = request.data;
|
||||
|
||||
this._handleMediasoupClientRequest(
|
||||
protooPeer, mediasoupRequest, accept, reject);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'mediasoup-notification':
|
||||
{
|
||||
accept();
|
||||
|
||||
const mediasoupNotification = request.data;
|
||||
|
||||
this._handleMediasoupClientNotification(
|
||||
protooPeer, mediasoupNotification);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'change-display-name':
|
||||
{
|
||||
accept();
|
||||
|
||||
const { displayName } = request.data;
|
||||
const { mediaPeer } = protooPeer.data;
|
||||
const oldDisplayName = mediaPeer.appData.displayName;
|
||||
|
||||
mediaPeer.appData.displayName = displayName;
|
||||
|
||||
// Spread to others via protoo.
|
||||
this._protooRoom.spread(
|
||||
'display-name-changed',
|
||||
{
|
||||
peerName : protooPeer.id,
|
||||
displayName : displayName,
|
||||
oldDisplayName : oldDisplayName
|
||||
},
|
||||
[ protooPeer ]);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
logger.error('unknown request.method "%s"', request.method);
|
||||
|
||||
reject(400, `unknown request.method "${request.method}"`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
protooPeer.on('close', () =>
|
||||
{
|
||||
logger.debug('protoo Peer "close" event [peerId:"%s"]', protooPeer.id);
|
||||
logger.debug('protoo Peer "close" event [peer:"%s"]', protooPeer.id);
|
||||
|
||||
this._protooRoom.spread(
|
||||
'removepeer',
|
||||
{
|
||||
peer :
|
||||
{
|
||||
id : protooPeer.id,
|
||||
msids : protooPeer.data.msids
|
||||
}
|
||||
});
|
||||
const { mediaPeer } = protooPeer.data;
|
||||
|
||||
// Close the media stuff.
|
||||
if (peerconnection)
|
||||
peerconnection.close();
|
||||
else
|
||||
if (mediaPeer && !mediaPeer.closed)
|
||||
mediaPeer.close();
|
||||
|
||||
// If this is the latest peer in the room, close the room.
|
||||
// However, wait a bit (for reconnections).
|
||||
// However wait a bit (for reconnections).
|
||||
setTimeout(() =>
|
||||
{
|
||||
if (this._mediaRoom && this._mediaRoom.closed)
|
||||
return;
|
||||
|
||||
if (this._protooRoom.peers.length === 0)
|
||||
if (this._mediaRoom.peers.length === 0)
|
||||
{
|
||||
logger.log(
|
||||
logger.info(
|
||||
'last peer in the room left, closing the room [roomId:"%s"]',
|
||||
this._roomId);
|
||||
|
||||
this.close();
|
||||
}
|
||||
}, 10000);
|
||||
}, 5000);
|
||||
});
|
||||
|
||||
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({ emit: false });
|
||||
else
|
||||
return videoRtpSender.enable({ emit: false });
|
||||
})
|
||||
.then(() =>
|
||||
{
|
||||
logger.log('"disableremotevideo" request succeed [disable:%s]',
|
||||
!!disable);
|
||||
|
||||
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)
|
||||
_handleMediaPeer(protooPeer, mediaPeer)
|
||||
{
|
||||
logger.debug('_sendOffer() [peerId:"%s"]', protooPeer.id);
|
||||
mediaPeer.on('notify', (notification) =>
|
||||
{
|
||||
protooPeer.send('mediasoup-notification', notification)
|
||||
.catch(() => {});
|
||||
});
|
||||
|
||||
let peerconnection = protooPeer.data.peerconnection;
|
||||
let mediaPeer = peerconnection.peer;
|
||||
mediaPeer.on('newtransport', (transport) =>
|
||||
{
|
||||
logger.info(
|
||||
'mediaPeer "newtransport" event [id:%s, direction:%s]',
|
||||
transport.id, transport.direction);
|
||||
|
||||
return Promise.resolve()
|
||||
.then(() =>
|
||||
// Update peers max sending bitrate.
|
||||
if (transport.direction === 'send')
|
||||
{
|
||||
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;
|
||||
this._updateMaxBitrate();
|
||||
|
||||
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)
|
||||
transport.on('close', () =>
|
||||
{
|
||||
let msid = rtpReceiver.rtpParameters.userParameters.msid.split(/\s/)[0];
|
||||
this._updateMaxBitrate();
|
||||
});
|
||||
}
|
||||
|
||||
setMsids.add(msid);
|
||||
this._handleMediaTransport(transport);
|
||||
});
|
||||
|
||||
mediaPeer.on('newproducer', (producer) =>
|
||||
{
|
||||
logger.info('mediaPeer "newproducer" event [id:%s]', producer.id);
|
||||
|
||||
this._handleMediaProducer(producer);
|
||||
});
|
||||
|
||||
mediaPeer.on('newconsumer', (consumer) =>
|
||||
{
|
||||
logger.info('mediaPeer "newconsumer" event [id:%s]', consumer.id);
|
||||
|
||||
this._handleMediaConsumer(consumer);
|
||||
});
|
||||
|
||||
// Also handle already existing Consumers.
|
||||
for (const consumer of mediaPeer.consumers)
|
||||
{
|
||||
logger.info('mediaPeer existing "consumer" [id:%s]', consumer.id);
|
||||
|
||||
this._handleMediaConsumer(consumer);
|
||||
}
|
||||
|
||||
// Notify about the existing active speaker.
|
||||
if (this._currentActiveSpeaker)
|
||||
{
|
||||
protooPeer.send(
|
||||
'active-speaker',
|
||||
{
|
||||
peerName : this._currentActiveSpeaker.name
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
_handleMediaTransport(transport)
|
||||
{
|
||||
transport.on('close', (originator) =>
|
||||
{
|
||||
logger.info(
|
||||
'Transport "close" event [originator:%s]', originator);
|
||||
});
|
||||
}
|
||||
|
||||
_handleMediaProducer(producer)
|
||||
{
|
||||
producer.on('close', (originator) =>
|
||||
{
|
||||
logger.info(
|
||||
'Producer "close" event [originator:%s]', originator);
|
||||
});
|
||||
|
||||
producer.on('pause', (originator) =>
|
||||
{
|
||||
logger.info(
|
||||
'Producer "pause" event [originator:%s]', originator);
|
||||
});
|
||||
|
||||
producer.on('resume', (originator) =>
|
||||
{
|
||||
logger.info(
|
||||
'Producer "resume" event [originator:%s]', originator);
|
||||
});
|
||||
}
|
||||
|
||||
_handleMediaConsumer(consumer)
|
||||
{
|
||||
consumer.on('close', (originator) =>
|
||||
{
|
||||
logger.info(
|
||||
'Consumer "close" event [originator:%s]', originator);
|
||||
});
|
||||
|
||||
consumer.on('pause', (originator) =>
|
||||
{
|
||||
logger.info(
|
||||
'Consumer "pause" event [originator:%s]', originator);
|
||||
});
|
||||
|
||||
consumer.on('resume', (originator) =>
|
||||
{
|
||||
logger.info(
|
||||
'Consumer "resume" event [originator:%s]', originator);
|
||||
});
|
||||
|
||||
consumer.on('effectiveprofilechange', (profile) =>
|
||||
{
|
||||
logger.info(
|
||||
'Consumer "effectiveprofilechange" event [profile:%s]', profile);
|
||||
});
|
||||
|
||||
// If video, initially make it 'low' profile unless this is for the current
|
||||
// active speaker.
|
||||
if (consumer.kind === 'video' && consumer.peer !== this._currentActiveSpeaker)
|
||||
consumer.setPreferredProfile('low');
|
||||
}
|
||||
|
||||
_handleMediasoupClientRequest(protooPeer, request, accept, reject)
|
||||
{
|
||||
logger.debug(
|
||||
'mediasoup-client request [method:%s, peer:"%s"]',
|
||||
request.method, protooPeer.id);
|
||||
|
||||
switch (request.method)
|
||||
{
|
||||
case 'queryRoom':
|
||||
{
|
||||
this._mediaRoom.receiveRequest(request)
|
||||
.then((response) => accept(response))
|
||||
.catch((error) => reject(500, error.toString()));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'join':
|
||||
{
|
||||
// TODO: Handle appData. Yes?
|
||||
const { peerName } = request;
|
||||
|
||||
if (peerName !== protooPeer.id)
|
||||
{
|
||||
reject(403, 'that is not your corresponding mediasoup Peer name');
|
||||
|
||||
break;
|
||||
}
|
||||
else if (protooPeer.data.mediaPeer)
|
||||
{
|
||||
reject(500, 'already have a mediasoup Peer');
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
protooPeer.data.msids = Array.from(setMsids);
|
||||
|
||||
// If msids changed, notify.
|
||||
let sameValues = (
|
||||
oldMsids.length == protooPeer.data.msids.length) &&
|
||||
oldMsids.every((element, index) =>
|
||||
this._mediaRoom.receiveRequest(request)
|
||||
.then((response) =>
|
||||
{
|
||||
return element === protooPeer.data.msids[index];
|
||||
accept(response);
|
||||
|
||||
// Get the newly created mediasoup Peer.
|
||||
const mediaPeer = this._mediaRoom.getPeerByName(peerName);
|
||||
|
||||
protooPeer.data.mediaPeer = mediaPeer;
|
||||
|
||||
this._handleMediaPeer(protooPeer, mediaPeer);
|
||||
})
|
||||
.catch((error) =>
|
||||
{
|
||||
reject(500, error.toString());
|
||||
});
|
||||
|
||||
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);
|
||||
break;
|
||||
}
|
||||
|
||||
logger.warn('resetting peerconnection');
|
||||
peerconnection.reset();
|
||||
});
|
||||
default:
|
||||
{
|
||||
const { mediaPeer } = protooPeer.data;
|
||||
|
||||
if (!mediaPeer)
|
||||
{
|
||||
logger.error(
|
||||
'cannot handle mediasoup request, no mediasoup Peer [method:"%s"]',
|
||||
request.method);
|
||||
|
||||
reject(400, 'no mediasoup Peer');
|
||||
}
|
||||
|
||||
mediaPeer.receiveRequest(request)
|
||||
.then((response) => accept(response))
|
||||
.catch((error) => reject(500, error.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_handleMediasoupClientNotification(protooPeer, notification)
|
||||
{
|
||||
logger.debug(
|
||||
'mediasoup-client notification [method:%s, peer:"%s"]',
|
||||
notification.method, protooPeer.id);
|
||||
|
||||
// NOTE: mediasoup-client just sends notifications with target 'peer',
|
||||
// so first of all, get the mediasoup Peer.
|
||||
const { mediaPeer } = protooPeer.data;
|
||||
|
||||
if (!mediaPeer)
|
||||
{
|
||||
logger.error(
|
||||
'cannot handle mediasoup notification, no mediasoup Peer [method:"%s"]',
|
||||
notification.method);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
mediaPeer.receiveNotification(notification);
|
||||
}
|
||||
|
||||
_updateMaxBitrate()
|
||||
@@ -514,8 +489,8 @@ class Room extends EventEmitter
|
||||
if (this._mediaRoom.closed)
|
||||
return;
|
||||
|
||||
let numPeers = this._mediaRoom.peers.length;
|
||||
let previousMaxBitrate = this._maxBitrate;
|
||||
const numPeers = this._mediaRoom.peers.length;
|
||||
const previousMaxBitrate = this._maxBitrate;
|
||||
let newMaxBitrate;
|
||||
|
||||
if (numPeers <= 2)
|
||||
@@ -530,29 +505,28 @@ class Room extends EventEmitter
|
||||
newMaxBitrate = MIN_BITRATE;
|
||||
}
|
||||
|
||||
if (newMaxBitrate === previousMaxBitrate)
|
||||
return;
|
||||
this._maxBitrate = newMaxBitrate;
|
||||
|
||||
for (let peer of this._mediaRoom.peers)
|
||||
for (const peer of this._mediaRoom.peers)
|
||||
{
|
||||
if (!peer.capabilities || peer.closed)
|
||||
continue;
|
||||
|
||||
for (let transport of peer.transports)
|
||||
for (const transport of peer.transports)
|
||||
{
|
||||
if (transport.closed)
|
||||
continue;
|
||||
|
||||
transport.setMaxBitrate(newMaxBitrate);
|
||||
if (transport.direction === 'send')
|
||||
{
|
||||
transport.setMaxBitrate(newMaxBitrate)
|
||||
.catch((error) =>
|
||||
{
|
||||
logger.error('transport.setMaxBitrate() failed: %s', String(error));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.log('_updateMaxBitrate() [num peers:%s, before:%skbps, now:%skbps]',
|
||||
logger.info(
|
||||
'_updateMaxBitrate() [num peers:%s, before:%skbps, now:%skbps]',
|
||||
numPeers,
|
||||
Math.round(previousMaxBitrate / 1000),
|
||||
Math.round(newMaxBitrate / 1000));
|
||||
|
||||
this._maxBitrate = newMaxBitrate;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+15
-16
@@ -2,7 +2,7 @@
|
||||
|
||||
const debug = require('debug');
|
||||
|
||||
const NAMESPACE = 'mediasoup-demo-server';
|
||||
const APP_NAME = 'mediasoup-demo-server';
|
||||
|
||||
class Logger
|
||||
{
|
||||
@@ -10,23 +10,25 @@ class Logger
|
||||
{
|
||||
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);
|
||||
this._debug = debug(`${APP_NAME}:${prefix}`);
|
||||
this._info = debug(`${APP_NAME}:INFO:${prefix}`);
|
||||
this._warn = debug(`${APP_NAME}:WARN:${prefix}`);
|
||||
this._error = debug(`${APP_NAME}:ERROR:${prefix}`);
|
||||
}
|
||||
else
|
||||
{
|
||||
this._debug = debug(NAMESPACE);
|
||||
this._log = debug(NAMESPACE + ':LOG');
|
||||
this._warn = debug(NAMESPACE + ':WARN');
|
||||
this._error = debug(NAMESPACE + ':ERROR');
|
||||
this._debug = debug(APP_NAME);
|
||||
this._info = debug(`${APP_NAME}:INFO`);
|
||||
this._warn = debug(`${APP_NAME}:WARN`);
|
||||
this._error = debug(`${APP_NAME}:ERROR`);
|
||||
}
|
||||
|
||||
/* eslint-disable no-console */
|
||||
this._debug.log = console.info.bind(console);
|
||||
this._log.log = console.info.bind(console);
|
||||
this._info.log = console.info.bind(console);
|
||||
this._warn.log = console.warn.bind(console);
|
||||
this._error.log = console.error.bind(console);
|
||||
/* eslint-enable no-console */
|
||||
}
|
||||
|
||||
get debug()
|
||||
@@ -34,9 +36,9 @@ class Logger
|
||||
return this._debug;
|
||||
}
|
||||
|
||||
get log()
|
||||
get info()
|
||||
{
|
||||
return this._log;
|
||||
return this._info;
|
||||
}
|
||||
|
||||
get warn()
|
||||
@@ -50,7 +52,4 @@ class Logger
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = function(prefix)
|
||||
{
|
||||
return new Logger(prefix);
|
||||
};
|
||||
module.exports = Logger;
|
||||
|
||||
Generated
+2169
-1885
File diff suppressed because it is too large
Load Diff
+5
-9
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mediasoup-demo-server",
|
||||
"version": "1.0.0",
|
||||
"version": "2.0.0",
|
||||
"private": true,
|
||||
"description": "mediasoup demo server",
|
||||
"author": "Iñaki Baz Castillo <ibc@aliax.net>",
|
||||
@@ -8,16 +8,12 @@
|
||||
"main": "lib/index.js",
|
||||
"dependencies": {
|
||||
"colors": "^1.1.2",
|
||||
"debug": "^2.6.8",
|
||||
"express": "^4.15.3",
|
||||
"mediasoup": "^1.2.5",
|
||||
"protoo-server": "^1.1.4"
|
||||
"debug": "^3.1.0",
|
||||
"express": "^4.16.2",
|
||||
"mediasoup": "^2.0.0",
|
||||
"protoo-server": "^2.0.5"
|
||||
},
|
||||
"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": "^4.0.0",
|
||||
"gulp-plumber": "^1.1.0"
|
||||
|
||||
+194
-60
@@ -6,11 +6,13 @@ process.title = 'mediasoup-demo-server';
|
||||
|
||||
const config = require('./config');
|
||||
|
||||
process.env.DEBUG = config.debug || '*LOG* *WARN* *ERROR*';
|
||||
process.env.DEBUG = config.debug || '*INFO* *WARN* *ERROR*';
|
||||
|
||||
/* eslint-disable no-console */
|
||||
console.log('- process.env.DEBUG:', process.env.DEBUG);
|
||||
console.log('- config.mediasoup.logLevel:', config.mediasoup.logLevel);
|
||||
console.log('- config.mediasoup.logTags:', config.mediasoup.logTags);
|
||||
/* eslint-enable no-console */
|
||||
|
||||
const fs = require('fs');
|
||||
const https = require('https');
|
||||
@@ -20,14 +22,16 @@ const mediasoup = require('mediasoup');
|
||||
const readline = require('readline');
|
||||
const colors = require('colors/safe');
|
||||
const repl = require('repl');
|
||||
const logger = require('./lib/logger')();
|
||||
const Logger = require('./lib/Logger');
|
||||
const Room = require('./lib/Room');
|
||||
|
||||
const logger = new Logger();
|
||||
|
||||
// Map of Room instances indexed by roomId.
|
||||
let rooms = new Map();
|
||||
const rooms = new Map();
|
||||
|
||||
// mediasoup server.
|
||||
let mediaServer = mediasoup.Server(
|
||||
const mediaServer = mediasoup.Server(
|
||||
{
|
||||
numWorkers : 1,
|
||||
logLevel : config.mediasoup.logLevel,
|
||||
@@ -41,30 +45,55 @@ let mediaServer = mediasoup.Server(
|
||||
});
|
||||
|
||||
global.SERVER = mediaServer;
|
||||
|
||||
mediaServer.on('newroom', (room) =>
|
||||
{
|
||||
global.ROOM = room;
|
||||
|
||||
room.on('newpeer', (peer) =>
|
||||
{
|
||||
global.PEER = peer;
|
||||
|
||||
if (peer.consumers.length > 0)
|
||||
global.CONSUMER = peer.consumers[peer.consumers.length - 1];
|
||||
|
||||
peer.on('newtransport', (transport) =>
|
||||
{
|
||||
global.TRANSPORT = transport;
|
||||
});
|
||||
|
||||
peer.on('newproducer', (producer) =>
|
||||
{
|
||||
global.PRODUCER = producer;
|
||||
});
|
||||
|
||||
peer.on('newconsumer', (consumer) =>
|
||||
{
|
||||
global.CONSUMER = consumer;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// HTTPS server for the protoo WebSocjet server.
|
||||
let tls =
|
||||
const 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, () =>
|
||||
const httpsServer = https.createServer(tls, (req, res) =>
|
||||
{
|
||||
logger.log('protoo WebSocket server running');
|
||||
res.writeHead(404, 'Not Here');
|
||||
res.end();
|
||||
});
|
||||
|
||||
httpsServer.listen(3443, '0.0.0.0', () =>
|
||||
{
|
||||
logger.info('protoo WebSocket server running');
|
||||
});
|
||||
|
||||
// Protoo WebSocket server.
|
||||
let webSocketServer = new protooServer.WebSocketServer(httpsServer,
|
||||
const webSocketServer = new protooServer.WebSocketServer(httpsServer,
|
||||
{
|
||||
maxReceivedFrameSize : 960000, // 960 KBytes.
|
||||
maxReceivedMessageSize : 960000,
|
||||
@@ -76,30 +105,48 @@ let webSocketServer = new protooServer.WebSocketServer(httpsServer,
|
||||
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'];
|
||||
const u = url.parse(info.request.url, true);
|
||||
const roomId = u.query['roomId'];
|
||||
const peerName = u.query['peerName'];
|
||||
|
||||
if (!roomId || !peerId)
|
||||
if (!roomId || !peerName)
|
||||
{
|
||||
logger.warn('connection request without roomId and/or peerId');
|
||||
logger.warn('connection request without roomId and/or peerName');
|
||||
|
||||
reject(400, 'Connection request without roomId and/or peerName');
|
||||
|
||||
reject(400, 'Connection request without roomId and/or peerId');
|
||||
return;
|
||||
}
|
||||
|
||||
logger.log('connection request [roomId:"%s", peerId:"%s"]', roomId, peerId);
|
||||
logger.info(
|
||||
'connection request [roomId:"%s", peerName:"%s"]', roomId, peerName);
|
||||
|
||||
let room;
|
||||
|
||||
// If an unknown roomId, create a new Room.
|
||||
if (!rooms.has(roomId))
|
||||
{
|
||||
logger.debug('creating a new Room [roomId:"%s"]', roomId);
|
||||
logger.info('creating a new Room [roomId:"%s"]', roomId);
|
||||
|
||||
let room = new Room(roomId, mediaServer);
|
||||
let logStatusTimer = setInterval(() =>
|
||||
try
|
||||
{
|
||||
room = new Room(roomId, mediaServer);
|
||||
|
||||
global.APP_ROOM = room;
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
logger.error('error creating a new Room: %s', error);
|
||||
|
||||
reject(error);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const logStatusTimer = setInterval(() =>
|
||||
{
|
||||
room.logStatus();
|
||||
}, 10000);
|
||||
}, 30000);
|
||||
|
||||
rooms.set(roomId, room);
|
||||
|
||||
@@ -109,15 +156,14 @@ webSocketServer.on('connectionrequest', (info, accept, reject) =>
|
||||
clearInterval(logStatusTimer);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
room = rooms.get(roomId);
|
||||
}
|
||||
|
||||
let room = rooms.get(roomId);
|
||||
let transport = accept();
|
||||
const transport = accept();
|
||||
|
||||
room.createProtooPeer(peerId, transport)
|
||||
.catch((error) =>
|
||||
{
|
||||
logger.error('error creating a protoo peer: %s', error);
|
||||
});
|
||||
room.handleConnection(peerName, transport);
|
||||
});
|
||||
|
||||
// Listen for keyboard input.
|
||||
@@ -125,6 +171,8 @@ webSocketServer.on('connectionrequest', (info, accept, reject) =>
|
||||
let cmd;
|
||||
let terminal;
|
||||
|
||||
openCommandConsole();
|
||||
|
||||
function openCommandConsole()
|
||||
{
|
||||
stdinLog('[opening Readline Command Console...]');
|
||||
@@ -133,10 +181,10 @@ function openCommandConsole()
|
||||
closeTerminal();
|
||||
|
||||
cmd = readline.createInterface(
|
||||
{
|
||||
input : process.stdin,
|
||||
output : process.stdout
|
||||
});
|
||||
{
|
||||
input : process.stdin,
|
||||
output : process.stdout
|
||||
});
|
||||
|
||||
cmd.on('SIGINT', () =>
|
||||
{
|
||||
@@ -162,10 +210,14 @@ function openCommandConsole()
|
||||
{
|
||||
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('- h, help : show this message');
|
||||
stdinLog('- sd, serverdump : execute server.dump()');
|
||||
stdinLog('- rd, roomdump : execute room.dump() for the latest created mediasoup Room');
|
||||
stdinLog('- pd, peerdump : execute peer.dump() for the latest created mediasoup Peer');
|
||||
stdinLog('- td, transportdump : execute transport.dump() for the latest created mediasoup Transport');
|
||||
stdinLog('- prd, producerdump : execute producer.dump() for the latest created mediasoup Producer');
|
||||
stdinLog('- cd, consumerdump : execute consumer.dump() for the latest created mediasoup Consumer');
|
||||
stdinLog('- t, terminal : open REPL Terminal');
|
||||
stdinLog('');
|
||||
readStdin();
|
||||
|
||||
@@ -178,7 +230,7 @@ function openCommandConsole()
|
||||
mediaServer.dump()
|
||||
.then((data) =>
|
||||
{
|
||||
stdinLog(`mediaServer.dump() succeeded:\n${JSON.stringify(data, null, ' ')}`);
|
||||
stdinLog(`server.dump() succeeded:\n${JSON.stringify(data, null, ' ')}`);
|
||||
readStdin();
|
||||
})
|
||||
.catch((error) =>
|
||||
@@ -202,14 +254,108 @@ function openCommandConsole()
|
||||
global.ROOM.dump()
|
||||
.then((data) =>
|
||||
{
|
||||
stdinLog('global.ROOM.dump() succeeded');
|
||||
stdinLog(`- peers:\n${JSON.stringify(data.peers, null, ' ')}`);
|
||||
stdinLog(`- num peers: ${data.peers.length}`);
|
||||
stdinLog(`room.dump() succeeded:\n${JSON.stringify(data, null, ' ')}`);
|
||||
readStdin();
|
||||
})
|
||||
.catch((error) =>
|
||||
{
|
||||
stdinError(`global.ROOM.dump() failed: ${error}`);
|
||||
stdinError(`room.dump() failed: ${error}`);
|
||||
readStdin();
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'pd':
|
||||
case 'peerdump':
|
||||
{
|
||||
if (!global.PEER)
|
||||
{
|
||||
readStdin();
|
||||
break;
|
||||
}
|
||||
|
||||
global.PEER.dump()
|
||||
.then((data) =>
|
||||
{
|
||||
stdinLog(`peer.dump() succeeded:\n${JSON.stringify(data, null, ' ')}`);
|
||||
readStdin();
|
||||
})
|
||||
.catch((error) =>
|
||||
{
|
||||
stdinError(`peer.dump() failed: ${error}`);
|
||||
readStdin();
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'td':
|
||||
case 'transportdump':
|
||||
{
|
||||
if (!global.TRANSPORT)
|
||||
{
|
||||
readStdin();
|
||||
break;
|
||||
}
|
||||
|
||||
global.TRANSPORT.dump()
|
||||
.then((data) =>
|
||||
{
|
||||
stdinLog(`transport.dump() succeeded:\n${JSON.stringify(data, null, ' ')}`);
|
||||
readStdin();
|
||||
})
|
||||
.catch((error) =>
|
||||
{
|
||||
stdinError(`transport.dump() failed: ${error}`);
|
||||
readStdin();
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'prd':
|
||||
case 'producerdump':
|
||||
{
|
||||
if (!global.PRODUCER)
|
||||
{
|
||||
readStdin();
|
||||
break;
|
||||
}
|
||||
|
||||
global.PRODUCER.dump()
|
||||
.then((data) =>
|
||||
{
|
||||
stdinLog(`producer.dump() succeeded:\n${JSON.stringify(data, null, ' ')}`);
|
||||
readStdin();
|
||||
})
|
||||
.catch((error) =>
|
||||
{
|
||||
stdinError(`producer.dump() failed: ${error}`);
|
||||
readStdin();
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'cd':
|
||||
case 'consumerdump':
|
||||
{
|
||||
if (!global.CONSUMER)
|
||||
{
|
||||
readStdin();
|
||||
break;
|
||||
}
|
||||
|
||||
global.CONSUMER.dump()
|
||||
.then((data) =>
|
||||
{
|
||||
stdinLog(`consumer.dump() succeeded:\n${JSON.stringify(data, null, ' ')}`);
|
||||
readStdin();
|
||||
})
|
||||
.catch((error) =>
|
||||
{
|
||||
stdinError(`consumer.dump() failed: ${error}`);
|
||||
readStdin();
|
||||
});
|
||||
|
||||
@@ -248,13 +394,10 @@ function openTerminal()
|
||||
prompt : 'terminal> ',
|
||||
useColors : true,
|
||||
useGlobal : true,
|
||||
ignoreUndefined : true
|
||||
ignoreUndefined : false
|
||||
});
|
||||
|
||||
terminal.on('exit', () =>
|
||||
{
|
||||
process.exit();
|
||||
});
|
||||
terminal.on('exit', () => openCommandConsole());
|
||||
}
|
||||
|
||||
function closeCommandConsole()
|
||||
@@ -276,23 +419,14 @@ function closeTerminal()
|
||||
}
|
||||
}
|
||||
|
||||
openCommandConsole();
|
||||
|
||||
// Export openCommandConsole function by typing 'c'.
|
||||
Object.defineProperty(global, 'c',
|
||||
{
|
||||
get : function()
|
||||
{
|
||||
openCommandConsole();
|
||||
}
|
||||
});
|
||||
|
||||
function stdinLog(msg)
|
||||
{
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(colors.green(msg));
|
||||
}
|
||||
|
||||
function stdinError(msg)
|
||||
{
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(colors.red.bold('ERROR: ') + colors.red(msg));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user