diff --git a/.jshintignore b/.jshintignore index 144faa0152..b7f4ab76bb 100644 --- a/.jshintignore +++ b/.jshintignore @@ -1,7 +1,7 @@ nuve/nuveClient/build nuve/nuveClient/dist -erizo_controller/erizoClient -spine/erizofc.js +erizo_controller/erizoClient/**/*.js +spine/**/*.js extras/basic_example/public/erizo.js extras/basic_example/nuve.js extras/basic_example/public/lib/ diff --git a/spine/NativeStack.js b/spine/NativeStack.js index a01daa7f23..6f58c7bb2e 100644 --- a/spine/NativeStack.js +++ b/spine/NativeStack.js @@ -1,85 +1,82 @@ -'use strict'; -var ErizoNativeConnection = require ('./nativeClient'); -// Logger -var logger = require('./logger').logger; -var log = logger.getLogger('NativeStack'); - -var NativeStack = function (spec) { - var that = {}; - log.info('Creating a NativeStack', spec); - - that.pcConfig = { - 'iceServers': [] - }; - - if (spec.iceServers !== undefined) { - that.pcConfig.iceServers = spec.iceServers; - } +const ErizoNativeConnection = require('./nativeClient'); +const logger = require('./logger').logger; + +const log = logger.getLogger('NativeStack'); + +let sessionId = 0; + +const NativeStack = (config) => { + const that = {}; + const configuration = Object.assign({}, config); + log.info('Creating a NativeStack', configuration); + + that.pcConfig = { + iceServers: [], + }; + + if (config.iceServers !== undefined) { + that.pcConfig.iceServers = configuration.iceServers; + } + + configuration.audio = configuration.audio || false; + configuration.video = configuration.video || false; + + that.peerConnection = ErizoNativeConnection.ErizoNativeConnection(configuration); + that.desc = {}; + that.callback = undefined; - if (spec.audio === undefined) { - spec.audio = false; + that.close = () => { + log.info('Close NATIVE'); + if (that.peerConnection) { + that.peerConnection.close(); + } else { + log.error('Trying to close with no underlying PC!'); } + }; - if (spec.video === undefined) { - spec.video = false; + that.stop = () => { + that.close(); + }; + + that.createOffer = () => { + log.info('NATIVESTACK: CreateOffer'); + }; + + that.addStream = () => { + log.info('NATIVESTACK: addStream'); + }; + + that.processSignalingMessage = (msg) => { + log.info('NATIVESTACK: processSignaling', msg.type); + that.peerConnection.processSignallingMessage(msg); + }; + + that.sendSignalingMessage = () => { + log.info('NATIVESTACK: Sending signaling Message'); + }; + + that.peerConnection.onaddstream = (stream) => { + if (that.onaddstream) { + that.onaddstream(stream); } + }; - that.peerConnection = ErizoNativeConnection.ErizoNativeConnection(spec) ; - that.desc = {}; - that.callback = undefined; - - that.close = function(){ - log.info('Close NATIVE'); - if (that.peerConnection){ - that.peerConnection.close(); - } else { - log.error('Trying to close with no underlying PC!'); - } - }; - - that.stop = function(){ - that.close(); - }; - - that.createOffer = function(){ - log.info('NATIVESTACK: CreateOffer'); - }; - - that.addStream = function(){ - log.info('NATIVESTACK: addStream'); - }; - - that.processSignalingMessage = function(msg){ - log.info('NATIVESTACK: processSignaling', msg.type); - that.peerConnection.processSignallingMessage(msg); - }; - - that.sendSignalingMessage = function(){ - log.info('NATIVESTACK: Sending signaling Message'); - }; - - that.peerConnection.onaddstream = function (stream) { - if (that.onaddstream) { - that.onaddstream(stream); - } - }; - - return that; + return that; }; -var sessionId = 0; -exports.buildConnection = function(spec){ - log.info('Creating Connection'); - spec.sessionId = sessionId++; - return NativeStack(spec); // jshint ignore:line +exports.buildConnection = (config) => { + log.info('Creating Connection'); + const configuration = Object.assign({}, config); + configuration.sessionId = sessionId; + sessionId += 1; + return NativeStack(configuration); // jshint ignore:line }; -exports.GetUserMedia = function(opt, callback){ - log.info('Fake getUserMedia to use with files', opt); - // if (that.peerConnection && opt.video.file){ - // that.peerConnection.prepareVideo(opt.video.file); - // } - callback(''); -}; -exports.getBrowser = function() { - return 'fake'; +exports.GetUserMedia = (opt, callback) => { + log.info('Fake getUserMedia to use with files', opt); + // if (that.peerConnection && opt.video.file){ + // that.peerConnection.prepareVideo(opt.video.file); + // } + callback(''); }; + +exports.getBrowser = () => 'fake'; diff --git a/spine/NativeStream.js b/spine/NativeStream.js index f748fdb2c8..a82796a784 100644 --- a/spine/NativeStream.js +++ b/spine/NativeStream.js @@ -1,5 +1,3 @@ -'use strict'; - const Erizo = require('./erizofc'); exports.Stream = (altConnection, specInput) => { diff --git a/spine/Spine.js b/spine/Spine.js new file mode 100644 index 0000000000..81b36953fa --- /dev/null +++ b/spine/Spine.js @@ -0,0 +1,88 @@ +const nativeConnection = require('./simpleNativeConnection'); +const logger = require('./logger').logger; + +exports.buildSpine = (configuration) => { + const that = {}; + that.nativeConnections = []; + + const log = logger.getLogger('Spine'); + const streamConfig = Object.assign({}, configuration); + + let streamPublishConfig; + let streamSubscribeConfig; + + if (streamConfig.publishConfig) { + streamPublishConfig = { + publishConfig: streamConfig.publishConfig, + serverUrl: streamConfig.basicExampleUrl, + }; + if (streamConfig.publishersAreSubscribers) { + streamPublishConfig.subscribeConfig = streamConfig.subscribeConfig; + } + log.info('publish Configuration: ', streamSubscribeConfig); + } + + if (streamConfig.subscribeConfig) { + streamSubscribeConfig = { + subscribeConfig: streamConfig.subscribeConfig, + serverUrl: streamConfig.basicExampleUrl, + }; + log.info('Subscribe Configuration: ', streamSubscribeConfig); + } + + const startSingleNativeClient = (clientConfig) => { + const nativeClient = nativeConnection.ErizoSimpleNativeConnection(clientConfig, + (msg) => { + log.debug('Callback from nativeClient', msg); + }, + (msg) => { + log.debug('Error from nativeClient', msg); + }); + that.nativeConnections.push(nativeClient); + }; + + const startStreams = (stConf, num, time) => { + let started = 0; + const interval = setInterval(() => { + started += 1; + if (started >= num) { + log.info('All streams have been started'); + clearInterval(interval); + } + log.info('Will start stream with config', stConf); + startSingleNativeClient(stConf); + }, time); + }; + + that.getAllStreamStats = () => { + const promises = []; + that.nativeConnections.forEach((connection) => { + promises.push(connection.getStats()); + }); + return Promise.all(promises); + }; + + that.getAllStreamStatuses = () => { + const statuses = []; + that.nativeConnections.forEach((connection) => { + statuses.push(connection.getStatus()); + }); + return statuses; + }; + + that.run = () => { + log.info('Starting ', streamConfig.numSubscribers, 'subscriber streams', + 'and', streamConfig.numPublishers, 'publisherStreams'); + if (streamSubscribeConfig && streamConfig.numSubscribers) { + startStreams(streamSubscribeConfig, + streamConfig.numSubscribers, + streamConfig.connectionCreationInterval); + } + if (streamPublishConfig && streamConfig.numPublishers) { + startStreams(streamPublishConfig, + streamConfig.numPublishers, + streamConfig.connectionCreationInterval); + } + }; + return that; +}; diff --git a/spine/logger.js b/spine/logger.js index 7329738717..846732693e 100644 --- a/spine/logger.js +++ b/spine/logger.js @@ -1,5 +1,6 @@ -var log4js = require('log4js'); -var logFile = './log4js_configuration.json'; +const log4js = require('log4js'); // eslint-disable-line import/no-extraneous-dependencies + +const logFile = './log4js_configuration.json'; log4js.configure(logFile); diff --git a/spine/nativeClient.js b/spine/nativeClient.js index 492a1eb202..cf776db968 100644 --- a/spine/nativeClient.js +++ b/spine/nativeClient.js @@ -1,197 +1,217 @@ -'use strict'; -var addon = require('./../erizoAPI/build/Release/addon'); -var licodeConfig = require('./../licode_config'); -var mediaConfig = require('./../rtp_media_config'); -var logger = require('./logger').logger; -var log = logger.getLogger('NativeClient'); + +const addon = require('./../erizoAPI/build/Release/addon'); // eslint-disable-line import/no-unresolved +const licodeConfig = require('./../licode_config'); +const mediaConfig = require('./../rtp_media_config'); +const logger = require('./logger').logger; + +const log = logger.getLogger('NativeClient'); GLOBAL.config = licodeConfig || {}; GLOBAL.mediaConfig = mediaConfig || {}; -var threadPool = new addon.ThreadPool(10); +const threadPool = new addon.ThreadPool(10); threadPool.start(); -var ioThreadPool = new addon.IOThreadPool(1); +const ioThreadPool = new addon.IOThreadPool(1); if (GLOBAL.config.erizo.useNicer) { ioThreadPool.start(); } -exports.ErizoNativeConnection = function (spec){ - var that = {}, - wrtc, - initWebRtcConnection, - syntheticInput, - externalInput, - oneToMany, - externalOutput; - - that.connected = false; - - var CONN_INITIAL = 101, - // CONN_STARTED = 102, - CONN_GATHERED = 103, - CONN_READY = 104, - // CONN_FINISHED = 105, - CONN_CANDIDATE = 201, - CONN_SDP = 202, - CONN_FAILED = 500; - - var generatePLIs = function(){ - externalOutput.interval = setInterval ( function(){ - wrtc.generatePLIPacket(); - },1000); - }; - - log.info('NativeConnection constructor', spec); - initWebRtcConnection = function (callback) { - wrtc.init( function (newStatus, mess){ - log.info('webrtc Addon status ', newStatus); - switch(newStatus) { - case CONN_INITIAL: - if (spec.video && spec.video.file && !externalInput){ - that.prepareVideo(spec.video.file); - } else if (spec.video && spec.video.recording && !externalOutput){ - that.prepareRecording(spec.video.recording); - } else if (spec.video && spec.video.synthetic && !syntheticInput) { - that.prepareSynthetic(spec.video.synthetic); - } else { - that.prepareNull(); - } - callback('callback', {type: 'started'}); - break; - - case CONN_SDP: - case CONN_GATHERED: - setTimeout(function(){ - callback('callback', {type: 'offer', sdp: mess}); - }, 100); - break; - - case CONN_CANDIDATE: - callback('callback', {type:'candidate', sdp: mess}); - break; - - case CONN_FAILED: - log.warn('Connection failed the ICE process'); - that.connected = false; - // callback('callback', {type: 'failed', sdp: mess}); - break; - - case CONN_READY: - log.info('Connection ready'); - that.connected = true; - if (externalInput !== undefined){ - log.info('Will start External Input'); - externalInput.init(); - } - if (syntheticInput !== undefined){ - log.info('Will start Synthetic Input'); - syntheticInput.init(); - } - if (externalOutput !== undefined){ - log.info('Will start External Output'); - externalOutput.init(); - generatePLIs(); - } - break; - } - }); - }; - - - wrtc = new addon.WebRtcConnection(threadPool, ioThreadPool, 'spine_' + spec.sessionId, - GLOBAL.config.erizo.stunserver, - GLOBAL.config.erizo.stunport, - GLOBAL.config.erizo.minport, - GLOBAL.config.erizo.maxport, - false, - JSON.stringify(GLOBAL.mediaConfig), - GLOBAL.config.erizo.useNicer, - GLOBAL.config.erizo.turnserver, - GLOBAL.config.erizo.turnport, - GLOBAL.config.erizo.turnusername, - GLOBAL.config.erizo.turnpass, - GLOBAL.config.erizo.networkinterface); - - that.createOffer = function () { - - }; - - that.prepareNull = function () { - log.info('Preparing null output'); - oneToMany = new addon.OneToManyProcessor(); - wrtc.setVideoReceiver(oneToMany); - wrtc.setAudioReceiver(oneToMany); - oneToMany.setPublisher(wrtc); - }; - - that.prepareVideo = function (url) { - log.info('Preparing video', url); - externalInput = new addon.ExternalInput(url); - externalInput.setAudioReceiver(wrtc); - externalInput.setVideoReceiver(wrtc); - }; - - that.prepareSynthetic = function (config) { - log.info('Preparing synthetic video', config); - syntheticInput = new addon.SyntheticInput(threadPool, - config.audioBitrate, - config.minVideoBitrate, - config.maxVideoBitrate); - syntheticInput.setAudioReceiver(wrtc); - syntheticInput.setVideoReceiver(wrtc); - syntheticInput.setFeedbackSource(wrtc); - }; - - that.prepareRecording = function (url) { - log.info('Preparing Recording', url); - externalOutput = new addon.ExternalOutput(url); - wrtc.setVideoReceiver(externalOutput); - wrtc.setAudioReceiver(externalOutput); - }; - - that.setRemoteDescription = function () { - log.info('RemoteDescription'); - }; - - that.processSignallingMessage = function(msg) { - log.info('Receiving message', msg.type); - if (msg.type === 'started'){ - initWebRtcConnection(function(mess, info){ - log.info('Message from wrtc', info.type); - if (info.type === 'offer') { - spec.callback({type:info.type, sdp: info.sdp}); - } - }, {}); - - var audioEnabled = true; - var videoEnabled = true; - var bundle = true; - wrtc.createOffer(audioEnabled, videoEnabled, bundle); - } else if (msg.type === 'answer'){ - setTimeout(function(){ - log.info('Passing delayed answer'); - wrtc.setRemoteSdp(msg.sdp); - that.onaddstream({stream:{active:true}}); - }, 10); +exports.ErizoNativeConnection = (config) => { + const that = {}; + let wrtc; + let syntheticInput; + let externalInput; + let oneToMany; + let externalOutput; + const configuration = Object.assign({}, config); + + that.connected = false; + + const CONN_INITIAL = 101; + // CONN_STARTED = 102 + const CONN_GATHERED = 103; + const CONN_READY = 104; + // CONN_FINISHED = 105, + const CONN_CANDIDATE = 201; + const CONN_SDP = 202; + const CONN_FAILED = 500; + + const generatePLIs = () => { + externalOutput.interval = setInterval(() => { + wrtc.generatePLIPacket(); + }, 1000); + }; + + log.info('NativeConnection constructor', configuration); + const initWebRtcConnection = (initConnectionCallback) => { + wrtc.init((newStatus, mess) => { + log.info('webrtc Addon status ', newStatus); + switch (newStatus) { + case CONN_INITIAL: + if (configuration.video && configuration.video.file && !externalInput) { + that.prepareVideo(configuration.video.file); + } else if (configuration.video && configuration.video.recording && !externalOutput) { + that.prepareRecording(configuration.video.recording); + } else if (configuration.video && configuration.video.synthetic && !syntheticInput) { + that.prepareSynthetic(configuration.video.synthetic); + } else { + that.prepareNull(); + } + initConnectionCallback('callback', { type: 'started' }); + break; + + case CONN_SDP: + case CONN_GATHERED: + setTimeout(() => { + initConnectionCallback('callback', { type: 'offer', sdp: mess }); + }, 100); + break; + + case CONN_CANDIDATE: + initConnectionCallback('callback', { type: 'candidate', sdp: mess }); + break; + + case CONN_FAILED: + log.warn('Connection failed the ICE process'); + that.connected = false; + // callback('callback', {type: 'failed', sdp: mess}); + break; + + case CONN_READY: + log.info('Connection ready'); + that.connected = true; + if (externalInput !== undefined) { + log.info('Will start External Input'); + externalInput.init(); + } + if (syntheticInput !== undefined) { + log.info('Will start Synthetic Input'); + syntheticInput.init(); + } + if (externalOutput !== undefined) { + log.info('Will start External Output'); + externalOutput.init(); + generatePLIs(); + } + break; + + default: + log.error('Unknown status', newStatus); + break; + } + }); + }; + + + wrtc = new addon.WebRtcConnection(threadPool, ioThreadPool, `spine_${configuration.sessionId}`, + GLOBAL.config.erizo.stunserver, + GLOBAL.config.erizo.stunport, + GLOBAL.config.erizo.minport, + GLOBAL.config.erizo.maxport, + false, + JSON.stringify(GLOBAL.mediaConfig), + GLOBAL.config.erizo.useNicer, + GLOBAL.config.erizo.turnserver, + GLOBAL.config.erizo.turnport, + GLOBAL.config.erizo.turnusername, + GLOBAL.config.erizo.turnpass, + GLOBAL.config.erizo.networkinterface); + + that.createOffer = () => { + }; + + that.prepareNull = () => { + log.info('Preparing null output'); + oneToMany = new addon.OneToManyProcessor(); + wrtc.setVideoReceiver(oneToMany); + wrtc.setAudioReceiver(oneToMany); + oneToMany.setPublisher(wrtc); + }; + + that.prepareVideo = (url) => { + log.info('Preparing video', url); + externalInput = new addon.ExternalInput(url); + externalInput.setAudioReceiver(wrtc); + externalInput.setVideoReceiver(wrtc); + }; + + that.prepareSynthetic = (spec) => { + log.info('Preparing synthetic video', config); + syntheticInput = new addon.SyntheticInput(threadPool, + spec.audioBitrate, + spec.minVideoBitrate, + spec.maxVideoBitrate); + syntheticInput.setAudioReceiver(wrtc); + syntheticInput.setVideoReceiver(wrtc); + syntheticInput.setFeedbackSource(wrtc); + }; + + that.prepareRecording = (url) => { + log.info('Preparing Recording', url); + externalOutput = new addon.ExternalOutput(url); + wrtc.setVideoReceiver(externalOutput); + wrtc.setAudioReceiver(externalOutput); + }; + + that.setRemoteDescription = () => { + log.info('RemoteDescription'); + }; + + that.processSignallingMessage = (signalingMsg) => { + log.info('Receiving message', signalingMsg.type); + if (signalingMsg.type === 'started') { + initWebRtcConnection((mess, info) => { + log.info('Message from wrtc', info.type); + if (info.type === 'offer') { + configuration.callback({ type: info.type, sdp: info.sdp }); } - }; - - that.close = function() { - if (externalOutput){ - externalOutput.close(); - if(externalOutput.interval) - clearInterval(externalOutput.interval); - } - if (externalInput!==undefined){ - externalInput.close(); - } - if (syntheticInput!==undefined){ - syntheticInput.close(); - } - that.connected = false; - wrtc.close(); - }; + }, {}); + + const audioEnabled = true; + const videoEnabled = true; + const bundle = true; + wrtc.createOffer(audioEnabled, videoEnabled, bundle); + } else if (signalingMsg.type === 'answer') { + setTimeout(() => { + log.info('Passing delayed answer'); + wrtc.setRemoteSdp(signalingMsg.sdp); + that.onaddstream({ stream: { active: true } }); + }, 10); + } + }; + + that.getStats = () => { + log.info('Requesting stats'); + return new Promise((resolve, reject) => { + if (!wrtc) { + reject('No connection present'); + return; + } + wrtc.getStats((statsString) => { + resolve(JSON.parse(statsString)); + }); + }); + }; + + that.close = () => { + log.info('Closing nativeConnection'); + if (externalOutput) { + externalOutput.close(); + if (externalOutput.interval) { + clearInterval(externalOutput.interval); + } + } + if (externalInput) { + externalInput.close(); + } + if (syntheticInput) { + syntheticInput.close(); + } + that.connected = false; + wrtc.close(); + wrtc = undefined; + }; - return that; + return that; }; diff --git a/spine/runSpineClients.js b/spine/runSpineClients.js index 4120ba477f..2a4e474189 100644 --- a/spine/runSpineClients.js +++ b/spine/runSpineClients.js @@ -1,105 +1,127 @@ -'use strict'; -var Getopt = require('node-getopt'); -var efc = require ('./simpleNativeConnection'); +const fs = require('fs'); +const Getopt = require('node-getopt'); // eslint-disable-line +const Spine = require('./Spine.js'); -var getopt = new Getopt([ - ['s' , 'stream-config=ARG' , 'file containing the stream config JSON'], - ['t' , 'time=ARG' , 'interval time to show stats (default 10 seconds)'], - ['h' , 'help' , 'display this help'] +const getopt = new Getopt([ + ['s', 'stream-config=ARG', 'file containing the stream config JSON (default is spineClientsConfig.json)'], + ['i', 'interval=ARG', 'interval time to show stats (default 10 seconds)'], + ['t', 'total-intervals=ARG', 'total test intervals (default 10 intervals)'], + ['o', 'output=ARG', 'output file for the stat digest (default is testResult.json)'], + ['h', 'help', 'display this help'], ]); +const opt = getopt.parse(process.argv.slice(2)); -var opt = getopt.parse(process.argv.slice(2)); +let streamConfig = 'spineClientsConfig.json'; +let statsIntervalTime = 10000; +let totalStatsIntervals = 10; +let statOutputFile = 'testResult.json'; +const optionKeys = Object.keys(opt.options); -var streamConfig; - -var statsInterval = 10000; - -for (var prop in opt.options) { - if (opt.options.hasOwnProperty(prop)) { - var value = opt.options[prop]; - switch (prop) { - case 'help': - getopt.showHelp(); - process.exit(0); - break; - case 'stream-config': - streamConfig = value; - break; - case 'time': - statsInterval = value * 1000; - break; - default: - console.log('Default'); - break; - } - } -} - -if (!streamConfig){ - streamConfig = 'spineClientsConfig.json'; -} +optionKeys.forEach((key) => { + const value = opt.options[key]; + switch (key) { + case 'help': + getopt.showHelp(); + process.exit(0); + break; + case 'stream-config': + streamConfig = value; + break; + case 'interval': + statsIntervalTime = value * 1000; + break; + case 'totalIntervals': + totalStatsIntervals = value; + break; + case 'output': + statOutputFile = value; + break; + default: + console.log('Default'); + break; + } +}); console.log('Loading stream config file', streamConfig); -streamConfig = require('./' + streamConfig); - -if (streamConfig.publishConfig){ - var streamPublishConfig = { - publishConfig: streamConfig.publishConfig, - serverUrl: streamConfig.basicExampleUrl - }; +streamConfig = require(`./${streamConfig}`); // eslint-disable-line - if (streamConfig.publishersAreSubscribers){ - streamPublishConfig.subscribeConfig = streamConfig.subscribeConfig; - } -} +const relevantStats = streamConfig.stats; +const SpineClient = Spine.buildSpine(streamConfig); -if (streamConfig.subscribeConfig){ - var streamSubscribeConfig = { - subscribeConfig : streamConfig.subscribeConfig, - serverUrl : streamConfig.basicExampleUrl - }; - console.log('StreamSubscribe', streamSubscribeConfig); -} - -var streams = []; +/* +JSON stats format +[ + [ + {'ssrc': {statkey:value ...} ...}, + ... + ] +... +] +*/ +const filterStats = (statsJson) => { + const results = {}; + relevantStats.forEach((targetStat) => { + results[targetStat] = []; + statsJson.forEach((clientStatEntry) => { + clientStatEntry.forEach((streamStatEntry) => { + const extractedStats = []; + const componentStatKeys = Object.keys(streamStatEntry); + componentStatKeys.forEach((componentStatKey) => { + const componentStats = streamStatEntry[componentStatKey]; + const statKeys = Object.keys(componentStats); + statKeys.forEach((statKey) => { + if (statKey === targetStat) { + extractedStats.push(componentStats[targetStat]); + } + }); + }); + results[targetStat].push(...extractedStats); + }); + }); + }); + return results; +}; -var startStreams = function(stConf, num, time){ - var started = 0; - var interval = setInterval(function(){ - if(++started>=num){ - console.log('All streams have been started'); - clearInterval(interval); - } - console.log('Will start stream with config', stConf); - streams.push(efc.ErizoSimpleNativeConnection (stConf, function(msg){ - console.log('Getting Callback', msg); - }, function(msg){ - console.error('Error message', msg); - })); - }, time); +const gatherStatuses = () => { + const statuses = SpineClient.getAllStreamStatuses(); + const statusResult = {}; + statusResult.up = 0; + statusResult.down = 0; + statuses.forEach((clientStatus) => { + clientStatus.forEach((streamStatus) => { + if (streamStatus === 'connected') { + statusResult.up += 1; + } else { + statusResult.down += 1; + } + }); + }); + return statusResult; }; -setInterval(function() { - var up = 0; - var down = 0; - for (var stream of streams) { - if (stream.getStatus() === 'connected') { - up++; - } else { - down++; - } - } - console.log('[STATS] up:', up, '; down:', down); -}, statsInterval); +const writeJsonToFile = (result) => { + fs.writeFile(statOutputFile, JSON.stringify(result), (err) => { + if (err) throw err; + }); + process.exit(0); +}; -console.log('Starting ', streamConfig.numSubscribers, 'subscriber streams', - 'and', streamConfig.numPublishers, 'publisherStreams'); +SpineClient.run(); -if (streamSubscribeConfig && streamConfig.numSubscribers) - startStreams(streamSubscribeConfig, - streamConfig.numSubscribers, - streamConfig.connectionCreationInterval); -if (streamPublishConfig && streamConfig.numPublishers) - startStreams(streamPublishConfig, - streamConfig.numPublishers, - streamConfig.connectionCreationInterval); +const statInterval = setInterval(() => { + SpineClient.getAllStreamStats().then((result) => { + const statsResult = filterStats(result); + const statusResult = gatherStatuses(); + const allResults = {}; + allResults.stats = statsResult; + allResults.alive = statusResult; + totalStatsIntervals -= 1; + console.log('Intervals to end:', totalStatsIntervals); + if (totalStatsIntervals === 0) { + clearInterval(statInterval); + writeJsonToFile(allResults); + } + }).catch((reason) => { + console.log('Stat gather failed:', reason); + }); +}, statsIntervalTime); diff --git a/spine/simpleNativeConnection.js b/spine/simpleNativeConnection.js index 3d61053e4b..1398bb7a2c 100644 --- a/spine/simpleNativeConnection.js +++ b/spine/simpleNativeConnection.js @@ -1,157 +1,174 @@ -'use strict'; process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'; // We need this for testing with self signed certs -var XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest, - nodeUrl = require ('url'), - Erizo = require('./erizofc'), - NativeStream = require ('./NativeStream.js'), - NativeStack = require ('./NativeStack.js'); +const XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest; // eslint-disable-line import/no-extraneous-dependencies +const newIo = require('socket.io-client'); // eslint-disable-line import/no-extraneous-dependencies +const nodeUrl = require('url'); +const Erizo = require('./erizofc'); +const NativeStream = require('./NativeStream.js'); +const NativeStack = require('./NativeStack.js'); +const logger = require('./logger').logger; -const newIo = require('socket.io-client'); const fakeConnection = NativeStack; -var logger = require('./logger').logger; -var log = logger.getLogger('ErizoSimpleNativeConnection'); - - -exports.ErizoSimpleNativeConnection = function (spec, callback, error){ - var that = {}; - - var localStream = {}; - var subscribedStreams = []; - var room = ''; - that.isActive = false; - localStream.getID = function() {return 0;}; - var createToken = function(userName, role, callback) { - - var req = new XMLHttpRequest(); - var theUrl = nodeUrl.parse(spec.serverUrl, true); - if (theUrl.protocol !== 'https') - log.warn('Protocol is not https'); - - if (theUrl.query.room){ - room = theUrl.query.room; - log.info('will Connect to Room', room); - } - - theUrl.pathname = theUrl.pathname + 'createToken/'; - - var url = nodeUrl.format(theUrl); - log.info('Url to createToken', url); - var body = {username: userName, role: role}; - - req.onreadystatechange = function () { - if (req.readyState === 4) { - if (req.status===200){ - callback(req.responseText); - }else{ - log.error('Could not get token'); - callback('error'); - } - } - }; - - req.open('POST', url, true); - req.setRequestHeader('Content-Type', 'application/json'); - req.send(JSON.stringify(body)); - }; - - var subscribeToStreams = function(streams){ - for (var index in streams){ - if (spec.subscribeConfig){ - log.info('Should subscribe', spec.subscribeConfig); - room.subscribe(streams[index], spec.subscribeConfig); - } +const log = logger.getLogger('ErizoSimpleNativeConnection'); + + +exports.ErizoSimpleNativeConnection = (spec, callback, error) => { + const that = {}; + + let localStream = {}; + const availableStreams = new Map(); + + let room = ''; + that.isActive = false; + localStream.getID = () => 0; + const createToken = (userName, role, tokenCallback) => { + const req = new XMLHttpRequest(); + const theUrl = nodeUrl.parse(spec.serverUrl, true); + if (theUrl.protocol !== 'https') { + log.warn('Protocol is not https'); + } + + if (theUrl.query.room) { + room = theUrl.query.room; + log.info('will Connect to Room', room); + } + + theUrl.pathname += 'createToken/'; + + const url = nodeUrl.format(theUrl); + log.info('Url to createToken', url); + const body = { username: userName, role }; + + req.onreadystatechange = () => { + if (req.readyState === 4) { + if (req.status === 200) { + tokenCallback(req.responseText); + } else { + log.error('Could not get token'); + tokenCallback('error'); } + } }; - var createConnection = function (){ + req.open('POST', url, true); + req.setRequestHeader('Content-Type', 'application/json'); + req.send(JSON.stringify(body)); + }; - createToken('name', 'presenter', function (token) { - log.info('Getting token', token); - if (token === 'error'){ - error('Could not get token from nuve in ' + spec.serverUrl); - return; + const subscribeToStreams = (streams) => { + streams.forEach((stream) => { + if (spec.subscribeConfig) { + log.info('Should subscribe', spec.subscribeConfig); + room.subscribe(stream, spec.subscribeConfig); + } + }); + }; + + const createConnection = () => { + createToken('name', 'presenter', (token) => { + log.info('Getting token', token); + if (token === 'error') { + error(`Could not get token from nuve in ${spec.serverUrl}`); + return; + } + room = Erizo.Room(newIo, fakeConnection, { token }); + + room.addEventListener('room-connected', (roomEvent) => { + log.info('Connected to room'); + callback('room-connected'); + that.isActive = true; + subscribeToStreams(roomEvent.streams); + if (spec.publishConfig) { + log.info('Will publish with config', spec.publishConfig); + localStream = NativeStream.Stream(fakeConnection, spec.publishConfig); + room.publish(localStream, spec.publishConfig, (id, message) => { + if (id === undefined) { + log.error('ERROR when publishing', message); + error(message); } - room = Erizo.Room(newIo, fakeConnection, {token: token}); - - room.addEventListener('room-connected', function(roomEvent) { - log.info('Connected to room'); - callback('room-connected'); - that.isActive = true; - subscribeToStreams(roomEvent.streams); - if (spec.publishConfig){ - log.info('Will publish with config', spec.publishConfig); - localStream = NativeStream.Stream(fakeConnection, spec.publishConfig); - room.publish(localStream, spec.publishConfig, function(id, message){ - if (id === undefined){ - log.error('ERROR when publishing', message); - error(message); - } - }); - } - }); - - room.addEventListener('stream-added', function(roomEvent) { - log.info('stream added', roomEvent.stream.getID()); - callback('stream-added'); - if (roomEvent.stream.getID()!==localStream.getID()){ - var streams = []; - streams.push(roomEvent.stream); - subscribeToStreams(streams); - } - - }); - - room.addEventListener('stream-removed', function(roomEvent) { - callback('stream-removed'); - if (roomEvent.stream.getID()!==localStream.getID()){ - room.unsubscribe(roomEvent.stream); - log.info('stream removed', roomEvent.stream.getID()); - } - - }); - - room.addEventListener('stream-subscribed', function(streamEvent) { - log.info('stream-subscribed'); - callback('stream-subscribed'); - subscribedStreams.push(streamEvent.stream); - }); - - room.addEventListener('room-error', function(roomEvent) { - log.error('Room Error', roomEvent.type, roomEvent.message); - error(roomEvent.message); - }); - - room.addEventListener('room-disconnected', function(roomEvent) { - log.info('Room Disconnected', roomEvent.type, roomEvent.message); - callback(roomEvent.type+roomEvent.message); - }); - - room.connect(); - }); - - that.sendData = function(){ - localStream.sendData({msg: 'Testing Data Connection'}); - }; - }; - - that.close = function(){ - log.info('Close'); - room.disconnect(); + }); + } + }); + + room.addEventListener('stream-added', (roomEvent) => { + log.info('stream added', roomEvent.stream.getID()); + callback('stream-added'); + if (roomEvent.stream.getID() !== localStream.getID()) { + const streams = []; + streams.push(roomEvent.stream); + subscribeToStreams(streams); + } else { + log.error('Adding localStream, id', localStream.getID()); + availableStreams.set(localStream.getID(), localStream); + } + }); + + room.addEventListener('stream-removed', (roomEvent) => { + callback('stream-removed'); + const eventStreamId = roomEvent.stream.getID(); + if (eventStreamId !== localStream.getID()) { + room.unsubscribe(roomEvent.stream); + log.info('stream removed', eventStreamId); + } + availableStreams.delete(eventStreamId); + }); + + room.addEventListener('stream-subscribed', (streamEvent) => { + log.info('stream-subscribed'); + callback('stream-subscribed'); + availableStreams.set(streamEvent.stream.getID(), streamEvent.stream); + }); + + room.addEventListener('room-error', (roomEvent) => { + log.error('Room Error', roomEvent.type, roomEvent.message); + error(roomEvent.message); + }); + + room.addEventListener('room-disconnected', (roomEvent) => { + log.info('Room Disconnected', roomEvent.type, roomEvent.message); + callback(roomEvent.type + roomEvent.message); + }); + + room.connect(); + }); + + that.sendData = () => { + localStream.sendData({ msg: 'Testing Data Connection' }); }; - - that.getStatus = function() { - if (subscribedStreams.length === 0) { - return 'disconnected'; + }; + + that.close = () => { + log.info('Close'); + room.disconnect(); + }; + + that.getStats = () => { + const promises = []; + availableStreams.forEach((stream, streamId) => { + if (stream.pc && stream.pc.peerConnection && stream.pc.peerConnection.connected) { + promises.push(stream.pc.peerConnection.getStats()); + } else { + log.warn('No stream to ask for stats: ', streamId); + promises.push('failed'); } - for (var stream of subscribedStreams) { - if (!stream.pc || !stream.pc.peerConnection || !stream.pc.peerConnection.connected) { - return 'disconnected'; - } + }); + return Promise.all(promises); + }; + + that.getStatus = () => { + if (availableStreams.length === 0) { + return ['disconnected']; + } + const connectionStatus = []; + availableStreams.forEach((stream) => { + if (!stream.pc || !stream.pc.peerConnection || !stream.pc.peerConnection.connected) { + connectionStatus.push('disconnected'); } - return 'connected'; - }; + connectionStatus.push('connected'); + }); + return connectionStatus; + }; - createConnection(); - return that; + createConnection(); + return that; }; diff --git a/spine/spineClientsConfig.json b/spine/spineClientsConfig.json index ed85cbb656..a7309e3260 100644 --- a/spine/spineClientsConfig.json +++ b/spine/spineClientsConfig.json @@ -18,7 +18,10 @@ }, "numPublishers": 0, "numSubscribers" : 1, - "publishersAreSubscribers" : true, - "connectionCreationInterval" : 500 - + "publishersAreSubscribers" : false, + "connectionCreationInterval" : 500, + "stats" : [ + "packetsLost", + "bitrateCalculated" + ] }