Implements audio levels.

This commit is contained in:
hristoterezov 2015-10-28 12:04:18 -05:00
parent 13268f9951
commit c38f93fc60
13 changed files with 2009 additions and 935 deletions

1
.gitignore vendored
View File

@ -4,3 +4,4 @@ node_modules
*.iml
.*.tmp
deploy-local.sh
.remote-sync.json

View File

@ -5,6 +5,7 @@ var RTCEvents = require("./service/RTC/RTCEvents");
var EventEmitter = require("events");
var JitsiConferenceEvents = require("./JitsiConferenceEvents");
var JitsiParticipant = require("./JitsiParticipant");
var Statistics = require("./modules/statistics/statistics");
/**
* Creates a JitsiConference object with the given name and properties.
@ -23,6 +24,8 @@ function JitsiConference(options) {
this.eventEmitter = new EventEmitter();
this.room = this.xmpp.createRoom(this.options.name, null, null, this.options.config);
this.rtc = new RTC(this.room, options);
if(!options.config.disableAudioLevels)
this.statistics = new Statistics();
setupListeners(this);
this.participants = {};
this.lastActiveSpeaker = null;
@ -205,8 +208,11 @@ JitsiConference.prototype.myUserId = function () {
* @param conference the conference
*/
function setupListeners(conference) {
conference.xmpp.addListener(XMPPEvents.CALL_INCOMING,
conference.rtc.onIncommingCall.bind(conference.rtc));
conference.xmpp.addListener(XMPPEvents.CALL_INCOMING, function (event) {
conference.rtc.onIncommingCall(event);
if(conference.statistics)
conference.statistics.startRemoteStats(event.peerconnection);
});
conference.room.addListener(XMPPEvents.REMOTE_STREAM_RECEIVED,
conference.rtc.createRemoteStream.bind(conference.rtc));
conference.rtc.addListener(StreamEventTypes.EVENT_TYPE_REMOTE_CREATED, function (stream) {
@ -255,6 +261,30 @@ function setupListeners(conference) {
conference.eventEmitter.emit(JitsiConferenceEvents.DISPLAY_NAME_CHANGED,
Strophe.getResourceFromJid(from), displayName);
});
if(conference.statistics) {
conference.statistics.addAudioLevelListener(function (ssrc, level) {
var userId = null;
if (ssrc === Statistics.LOCAL_JID) {
userId = conference.myUserId();
} else {
var jid = conference.room.getJidBySSRC(ssrc);
if (!jid)
return;
userId = Strophe.getResourceFromJid(jid);
}
conference.eventEmitter.emit(JitsiConferenceEvents.TRACK_AUDIO_LEVEL_CHANGED,
userId, level);
});
conference.rtc.addListener(StreamEventTypes.EVENT_TYPE_LOCAL_CREATED, function (stream) {
conference.statistics.startLocalStats(stream);
});
conference.xmpp.addListener(XMPPEvents.DISPOSE_CONFERENCE,
function () {
conference.statistics.dispose();
});
}
}

View File

@ -40,7 +40,7 @@ The ```options``` parameter is JS object with the following properties:
* ```JitsiMeetJS.JitsiConnection``` - the ```JitsiConnection``` constructor. You can use that to create new server connection.
* ```JitsiMeetJS.events``` - JS object that contains all events used by the API. You will need that JS object when you try to subscribe for connection or conference events.
We have two event types - connection and conference. You can access the events with the following code ```JitsiMeetJS.events.<event_type>.<event_name>```.
For example if you want to use the conference event that is fired when somebody leave conference you can use the following code - ```JitsiMeetJS.events.conference.USER_LEFT```.
@ -48,7 +48,7 @@ The ```options``` parameter is JS object with the following properties:
1. conference
- TRACK_ADDED - remote stream received. (parameters - JitsiTrack)
- TRACK_REMOVED - remote stream removed. (parameters - JitsiTrack)
- TRACK_MUTE_CHANGED - JitsiTrack was muted or unmuted. (parameters - JitsiTrack)
- TRACK_MUTE_CHANGED - JitsiTrack was muted or unmuted. (parameters - JitsiTrack)
- ACTIVE_SPEAKER_CHANGED - the active speaker is changed. (parameters - id(string))
- USER_JOINED - new user joined a conference. (parameters - id(string))
- USER_LEFT - a participant left conference. (parameters - id(string))
@ -58,13 +58,13 @@ The ```options``` parameter is JS object with the following properties:
- IN_LAST_N_CHANGED - passes boolean property that shows whether the local user is included in last n set of any other user or not. (parameters - boolean)
- CONFERENCE_JOINED - notifies the local user that he joined the conference successfully. (no parameters)
- CONFERENCE_LEFT - notifies the local user that he left the conference successfully. (no parameters)
2. connection
- CONNECTION_FAILED - indicates that the server connection failed.
- CONNECTION_ESTABLISHED - indicates that we have successfully established server connection.
- CONNECTION_DISCONNECTED - indicates that we are disconnected.
- WRONG_STATE - indicates that the user has performed action that can't be executed because the connection is in wrong state.
* ```JitsiMeetJS.errors``` - JS object that contains all errors used by the API. You can use that object to check the reported errors from the API
We have two error types - connection and conference. You can access the events with the following code ```JitsiMeetJS.errors.<error_type>.<error_name>```.
For example if you want to use the conference event that is fired when somebody leave conference you can use the following code - ```JitsiMeetJS.errors.conference.PASSWORD_REQUIRED```.
@ -89,30 +89,31 @@ This objects represents the server connection. You can create new ```JitsiConnec
- appID - identification for the provider of Jitsi Meet video conferencing services. **NOTE: not implemented yet. You can safely pass ```null```**
- token - secret generated by the provider of Jitsi Meet video conferencing services. The token will be send to the provider from the Jitsi Meet server deployment for authorization of the current client. **NOTE: not implemented yet. You can safely pass ```null```**
- options - JS object with configuration options for the server connection. You can change the following properties there:
1. bosh -
2. hosts - JS Object
1. bosh -
2. hosts - JS Object
- domain
- muc
- bridge
- anonymousdomain
3. useStunTurn -
3. useStunTurn -
2. connect(options) - establish server connection
- options - JS Object with ```id``` and ```password``` properties.
3. disconnect() - destroys the server connection
4. initJitsiConference(name, options) - creates new ```JitsiConference``` object.
- name - the name of the conference
- options - JS object with configuration options for the conference. You can change the following properties there:
1. devices - array with the devices - "video" and "audio" that will be passed to GUM. If that property is not set GUM will try to get all available devices.
1. devices - array with the devices - "video" and "audio" that will be passed to GUM. If that property is not set GUM will try to get all available devices.
2. resolution - the prefered resolution for the local video.
3. openSctp - boolean property. Enables/disables datachannel support. **NOTE: we recommend to set that option to true**
4. disableAudioLevels - boolean property. Enables/disables audio levels.
5. addEventListener(event, listener) - Subscribes the passed listener to the event.
- event - one of the events from ```JitsiMeetJS.events.connection``` object.
- listener - handler for the event.
6. removeEventListener(event, listener) - Removes event listener.
- event - the event
- listener - the listener that will be removed.
@ -124,24 +125,24 @@ The object represents a conference. We have the following methods to control the
1. join(password) - Joins the conference
- password - string of the password. This parameter is not mandatory.
2. leave() - leaves the conference
3. createLocalTracks(options) - Creates the media tracks and returns them trough ```Promise``` object.
- options - JS object with configuration options for the local media tracks. You can change the following properties there:
1. devices - array with the devices - "video" and "audio" that will be passed to GUM. If that property is not set GUM will try to get all available devices.
1. devices - array with the devices - "video" and "audio" that will be passed to GUM. If that property is not set GUM will try to get all available devices.
2. resolution - the prefered resolution for the local video.
4. getLocalTracks() - Returns array with JitsiTrack objects for the local streams.
5. addEventListener(event, listener) - Subscribes the passed listener to the event.
- event - one of the events from ```JitsiMeetJS.events.conference``` object.
- listener - handler for the event.
6. removeEventListener(event, listener) - Removes event listener.
- event - the event
- listener - the listener that will be removed.
7. on(event, listener) - alias for addEventListener
8. off(event, listener) - alias for removeEventListener
@ -157,23 +158,23 @@ The object represents a conference. We have the following methods to control the
12. sendCommand(name, values) - sends user defined system command to the other participants
- name - the name of the command.
- values - JS object. The object has the following structure:
```
{
value: the_value_of_the_command,
attributes: {},// map with keys the name of the attribute and values - the values of the attributes.
children: [] // array with JS object with the same structure.
}
```
NOTE: When you use that method the passed object will be added in every system message that is sent to the other participants. It might be sent more than once.
@ -185,11 +186,11 @@ The object represents a conference. We have the following methods to control the
15. addCommandListener(command, handler) - adds listener
- command - string for the name of the command
- handler(values) - the listener that will be called when a command is received from another participant.
- handler(values) - the listener that will be called when a command is received from another participant.
16. removeCommandListener(command) - removes the listeners for the specified command
- command - the name of the command
JitsiTrack
======
@ -199,18 +200,18 @@ We have the following methods for controling the tracks:
1.getType() - returns string with the type of the track( "video" for the video tracks and "audio" for the audio tracks)
2.mute() - mutes the track.
2.mute() - mutes the track.
Note: This method is implemented only for the local tracks.
3.unmute() - unmutes the track.
3.unmute() - unmutes the track.
Note: This method is implemented only for the local tracks.
4. attach(container) - attaches the track to the given container.
5. detach(container) - removes the track from the container.
6. stop() - stop sending the track to the other participants in the conference.
@ -224,7 +225,7 @@ Note: This method is implemented only for the local tracks.
8. getId() - returns unique string for the track.
Getting Started
==============
@ -252,7 +253,7 @@ connection.addEventListener(JitsiMeetJS.events.connection.CONNECTION_DISCONNECTE
connection.connect();
```
4. After you receive the ```CONNECTION_ESTABLISHED``` event you are to create the ```JitsiConference``` object and
4. After you receive the ```CONNECTION_ESTABLISHED``` event you are to create the ```JitsiConference``` object and
also you may want to attach listeners for conference events (we are going to add handlers for remote track, conference joined, etc. ):
@ -277,10 +278,3 @@ room.join();
```
After that step you are in the conference. Now you can continue with adding some code that will handle the events and manage the conference.

View File

@ -19,7 +19,8 @@ var options = {
}
var confOptions = {
openSctp: true
openSctp: true,
disableAudioLevels: true
}
/**
@ -29,7 +30,6 @@ var confOptions = {
function onLocalTracks(tracks)
{
localTracks = tracks;
console.log(tracks);
tracks[0].attach($("#localAudio"));
tracks[1].attach($("#localVideo"));
for(var i = 0; i < localTracks.length; i++)
@ -90,6 +90,10 @@ function onConnectionSuccess(){
room.on(JitsiMeetJS.events.conference.DISPLAY_NAME_CHANGED, function (userID, displayName) {
console.debug(userID + " - " + displayName);
});
room.on(JitsiMeetJS.events.conference.TRACK_AUDIO_LEVEL_CHANGED,
function(userID, audioLevel){
// console.log(userID + " - " + audioLevel);
});
room.join();
};
@ -128,7 +132,3 @@ connection.addEventListener(JitsiMeetJS.events.connection.CONNECTION_FAILED, onC
connection.addEventListener(JitsiMeetJS.events.connection.CONNECTION_DISCONNECTED, disconnect);
connection.connect();

View File

@ -15,4 +15,4 @@
<video id="localVideo" autoplay="true"></video>
<!--<audio id="localAudio" autoplay="true" muted="true"></audio>-->
</body>
</html>
</html>

File diff suppressed because it is too large Load Diff

View File

@ -104,22 +104,11 @@ RTC.prototype.pinEndpoint = function (id) {
this.dataChannels.handlePinnedEndpointEvent(id);
}
RTC.prototype.addStreamListener = function (listener, eventType) {
this.eventEmitter.on(eventType, listener);
};
RTC.prototype.addListener = function (type, listener) {
this.eventEmitter.on(type, listener);
};
RTC.prototype.removeListener = function (listener, eventType) {
this.eventEmitter.removeListener(eventType, listener);
};
RTC.prototype.removeStreamListener = function (listener, eventType) {
if(!(eventType instanceof StreamEventTypes))
throw "Illegal argument";
RTC.prototype.removeListener = function (eventType, listener) {
this.eventEmitter.removeListener(eventType, listener);
};

View File

@ -5,6 +5,8 @@
var RTCBrowserType = require('../RTC/RTCBrowserType');
var LOCAL_JID = require("../../service/statistics/constants").LOCAL_JID;
/**
* Size of the webaudio analyzer buffer.
* @type {number}
@ -80,7 +82,7 @@ function LocalStatsCollector(stream, interval, statisticsService, eventEmitter)
* Starts the collecting the statistics.
*/
LocalStatsCollector.prototype.start = function () {
if (config.disableAudioLevels || !window.AudioContext ||
if (!window.AudioContext ||
RTCBrowserType.isTemasysPluginUsed())
return;
@ -105,7 +107,7 @@ LocalStatsCollector.prototype.start = function () {
self.audioLevel = animateLevel(audioLevel, self.audioLevel);
self.eventEmitter.emit(
"statistics.audioLevel",
self.statisticsService.LOCAL_JID,
LOCAL_JID,
self.audioLevel);
}
},

View File

@ -5,6 +5,39 @@ var RTCBrowserType = require("../RTC/RTCBrowserType");
/* Whether we support the browser we are running into for logging statistics */
var browserSupported = RTCBrowserType.isChrome() ||
RTCBrowserType.isOpera();
var keyMap = {};
keyMap[RTCBrowserType.RTC_BROWSER_FIREFOX] = {
"ssrc": "ssrc",
"packetsReceived": "packetsReceived",
"packetsLost": "packetsLost",
"packetsSent": "packetsSent",
"bytesReceived": "bytesReceived",
"bytesSent": "bytesSent"
};
keyMap[RTCBrowserType.RTC_BROWSER_CHROME] = {
"receiveBandwidth": "googAvailableReceiveBandwidth",
"sendBandwidth": "googAvailableSendBandwidth",
"remoteAddress": "googRemoteAddress",
"transportType": "googTransportType",
"localAddress": "googLocalAddress",
"activeConnection": "googActiveConnection",
"ssrc": "ssrc",
"packetsReceived": "packetsReceived",
"packetsSent": "packetsSent",
"packetsLost": "packetsLost",
"bytesReceived": "bytesReceived",
"bytesSent": "bytesSent",
"googFrameHeightReceived": "googFrameHeightReceived",
"googFrameWidthReceived": "googFrameWidthReceived",
"googFrameHeightSent": "googFrameHeightSent",
"googFrameWidthSent": "googFrameWidthSent",
"audioInputLevel": "audioInputLevel",
"audioOutputLevel": "audioOutputLevel"
};
keyMap[RTCBrowserType.RTC_BROWSER_OPERA] =
keyMap[RTCBrowserType.RTC_BROWSER_CHROME];
/**
* Calculates packet lost percent using the number of lost packets and the
* number of all packet.
@ -27,6 +60,42 @@ function getStatValue(item, name) {
item.stat(key) : item[key];
}
function formatAudioLevel(audioLevel) {
return Math.min(Math.max(audioLevel, 0), 1);
}
/**
* Checks whether a certain record should be included in the logged statistics.
*/
function acceptStat(reportId, reportType, statName) {
if (reportType == "googCandidatePair" && statName == "googChannelId")
return false;
if (reportType == "ssrc") {
if (statName == "googTrackId" ||
statName == "transportId" ||
statName == "ssrc")
return false;
}
return true;
}
/**
* Checks whether a certain record should be included in the logged statistics.
*/
function acceptReport(id, type) {
if (id.substring(0, 15) == "googCertificate" ||
id.substring(0, 9) == "googTrack" ||
id.substring(0, 20) == "googLibjingleSession")
return false;
if (type == "googComponent")
return false;
return true;
}
/**
* Peer statistics data holder.
* @constructor
@ -39,42 +108,22 @@ function PeerStats()
this.ssrc2resolution = {};
}
/**
* The bandwidth
* @type {{}}
*/
PeerStats.bandwidth = {};
/**
* The bit rate
* @type {{}}
*/
PeerStats.bitrate = {};
/**
* The packet loss rate
* @type {{}}
*/
PeerStats.packetLoss = null;
/**
* Sets packets loss rate for given <tt>ssrc</tt> that blong to the peer
* represented by this instance.
* @param ssrc audio or video RTP stream SSRC.
* @param lossRate new packet loss rate value to be set.
*/
PeerStats.prototype.setSsrcLoss = function (ssrc, lossRate)
PeerStats.prototype.setSsrcLoss = function (lossRate)
{
this.ssrc2Loss[ssrc] = lossRate;
this.ssrc2Loss = lossRate;
};
/**
* Sets resolution for given <tt>ssrc</tt> that belong to the peer
* Sets resolution that belong to the ssrc
* represented by this instance.
* @param ssrc audio or video RTP stream SSRC.
* @param resolution new resolution value to be set.
*/
PeerStats.prototype.setSsrcResolution = function (ssrc, resolution)
PeerStats.prototype.setSsrcResolution = function (resolution)
{
if(resolution === null && this.ssrc2resolution[ssrc])
{
@ -116,17 +165,35 @@ PeerStats.prototype.setSsrcAudioLevel = function (ssrc, audioLevel)
this.ssrc2AudioLevel[ssrc] = formatAudioLevel(audioLevel);
};
function formatAudioLevel(audioLevel) {
return Math.min(Math.max(audioLevel, 0), 1);
function ConferenceStats() {
/**
* The bandwidth
* @type {{}}
*/
this.bandwidth = {};
/**
* The bit rate
* @type {{}}
*/
this.bitrate = {};
/**
* The packet loss rate
* @type {{}}
*/
this.packetLoss = null;
/**
* Array with the transport information.
* @type {Array}
*/
this.transport = [];
}
/**
* Array with the transport information.
* @type {Array}
*/
PeerStats.transport = [];
/**
* <tt>StatsCollector</tt> registers for stats updates of given
* <tt>peerconnection</tt> in given <tt>interval</tt>. On each update particular
@ -138,9 +205,10 @@ PeerStats.transport = [];
* @param interval stats refresh interval given in ms.
* @param {function(StatsCollector)} audioLevelsUpdateCallback the callback
* called on stats update.
* @param config {object} supports the following properties - disableAudioLevels, disableStats, logStats
* @constructor
*/
function StatsCollector(peerconnection, audioLevelsInterval, statsInterval, eventEmitter)
function StatsCollector(peerconnection, audioLevelsInterval, statsInterval, eventEmitter, config)
{
this.peerconnection = peerconnection;
this.baselineAudioLevelsReport = null;
@ -149,6 +217,8 @@ function StatsCollector(peerconnection, audioLevelsInterval, statsInterval, even
this.baselineStatsReport = null;
this.audioLevelsIntervalId = null;
this.eventEmitter = eventEmitter;
this.config = config || {};
this.conferenceStats = new ConferenceStats();
/**
* Gather PeerConnection stats once every this many milliseconds.
@ -185,8 +255,8 @@ function StatsCollector(peerconnection, audioLevelsInterval, statsInterval, even
this.statsIntervalId = null;
this.statsIntervalMilis = statsInterval;
// Map of jids to PeerStats
this.jid2stats = {};
// Map of ssrcs to PeerStats
this.ssrc2stats = {};
}
module.exports = StatsCollector;
@ -235,119 +305,85 @@ StatsCollector.prototype.errorCallback = function (error)
StatsCollector.prototype.start = function ()
{
var self = this;
if (!config.disableAudioLevels) {
this.audioLevelsIntervalId = setInterval(
function () {
// Interval updates
self.peerconnection.getStats(
function (report) {
var results = null;
if (!report || !report.result ||
typeof report.result != 'function') {
results = report;
}
else {
results = report.result();
}
//console.error("Got interval report", results);
self.currentAudioLevelsReport = results;
self.processAudioLevelReport();
self.baselineAudioLevelsReport =
self.currentAudioLevelsReport;
},
self.errorCallback
);
},
self.audioLevelsIntervalMilis
);
}
if (!config.disableStats && browserSupported) {
this.statsIntervalId = setInterval(
function () {
// Interval updates
self.peerconnection.getStats(
function (report) {
var results = null;
if (!report || !report.result ||
typeof report.result != 'function') {
//firefox
results = report;
}
else {
//chrome
results = report.result();
}
//console.error("Got interval report", results);
self.currentStatsReport = results;
try {
self.processStatsReport();
}
catch (e) {
console.error("Unsupported key:" + e, e);
}
self.baselineStatsReport = self.currentStatsReport;
},
self.errorCallback
);
},
self.statsIntervalMilis
);
}
if (config.logStats && browserSupported) {
this.gatherStatsIntervalId = setInterval(
function () {
self.peerconnection.getStats(
function (report) {
self.addStatsToBeLogged(report.result());
},
function () {
this.audioLevelsIntervalId = setInterval(
function () {
// Interval updates
self.peerconnection.getStats(
function (report) {
var results = null;
if (!report || !report.result ||
typeof report.result != 'function') {
results = report;
}
);
},
this.GATHER_INTERVAL
);
else {
results = report.result();
}
//console.error("Got interval report", results);
self.currentAudioLevelsReport = results;
self.processAudioLevelReport();
self.baselineAudioLevelsReport =
self.currentAudioLevelsReport;
},
self.errorCallback
);
},
self.audioLevelsIntervalMilis
);
this.logStatsIntervalId = setInterval(
function() { self.logStats(); },
this.LOG_INTERVAL);
}
// if (!this.config.disableStats && browserSupported) {
// this.statsIntervalId = setInterval(
// function () {
// // Interval updates
// self.peerconnection.getStats(
// function (report) {
// var results = null;
// if (!report || !report.result ||
// typeof report.result != 'function') {
// //firefox
// results = report;
// }
// else {
// //chrome
// results = report.result();
// }
// //console.error("Got interval report", results);
// self.currentStatsReport = results;
// try {
// self.processStatsReport();
// }
// catch (e) {
// console.error("Unsupported key:" + e, e);
// }
//
// self.baselineStatsReport = self.currentStatsReport;
// },
// self.errorCallback
// );
// },
// self.statsIntervalMilis
// );
// }
//
// if (this.config.logStats && browserSupported) {
// this.gatherStatsIntervalId = setInterval(
// function () {
// self.peerconnection.getStats(
// function (report) {
// self.addStatsToBeLogged(report.result());
// },
// function () {
// }
// );
// },
// this.GATHER_INTERVAL
// );
//
// this.logStatsIntervalId = setInterval(
// function() { self.logStats(); },
// this.LOG_INTERVAL);
// }
};
/**
* Checks whether a certain record should be included in the logged statistics.
*/
function acceptStat(reportId, reportType, statName) {
if (reportType == "googCandidatePair" && statName == "googChannelId")
return false;
if (reportType == "ssrc") {
if (statName == "googTrackId" ||
statName == "transportId" ||
statName == "ssrc")
return false;
}
return true;
}
/**
* Checks whether a certain record should be included in the logged statistics.
*/
function acceptReport(id, type) {
if (id.substring(0, 15) == "googCertificate" ||
id.substring(0, 9) == "googTrack" ||
id.substring(0, 20) == "googLibjingleSession")
return false;
if (type == "googComponent")
return false;
return true;
}
/**
* Converts the stats to the format used for logging, and saves the data in
* this.statsToBeLogged.
@ -380,45 +416,16 @@ StatsCollector.prototype.addStatsToBeLogged = function (reports) {
});
};
StatsCollector.prototype.logStats = function () {
if(!APP.xmpp.sendLogs(this.statsToBeLogged))
return;
// Reset the stats
this.statsToBeLogged.stats = {};
this.statsToBeLogged.timestamps = [];
};
var keyMap = {};
keyMap[RTCBrowserType.RTC_BROWSER_FIREFOX] = {
"ssrc": "ssrc",
"packetsReceived": "packetsReceived",
"packetsLost": "packetsLost",
"packetsSent": "packetsSent",
"bytesReceived": "bytesReceived",
"bytesSent": "bytesSent"
};
keyMap[RTCBrowserType.RTC_BROWSER_CHROME] = {
"receiveBandwidth": "googAvailableReceiveBandwidth",
"sendBandwidth": "googAvailableSendBandwidth",
"remoteAddress": "googRemoteAddress",
"transportType": "googTransportType",
"localAddress": "googLocalAddress",
"activeConnection": "googActiveConnection",
"ssrc": "ssrc",
"packetsReceived": "packetsReceived",
"packetsSent": "packetsSent",
"packetsLost": "packetsLost",
"bytesReceived": "bytesReceived",
"bytesSent": "bytesSent",
"googFrameHeightReceived": "googFrameHeightReceived",
"googFrameWidthReceived": "googFrameWidthReceived",
"googFrameHeightSent": "googFrameHeightSent",
"googFrameWidthSent": "googFrameWidthSent",
"audioInputLevel": "audioInputLevel",
"audioOutputLevel": "audioOutputLevel"
};
keyMap[RTCBrowserType.RTC_BROWSER_OPERA] =
keyMap[RTCBrowserType.RTC_BROWSER_CHROME];
//FIXME:
//StatsCollector.prototype.logStats = function () {
//
// if(!APP.xmpp.sendLogs(this.statsToBeLogged))
// return;
// // Reset the stats
// this.statsToBeLogged.stats = {};
// this.statsToBeLogged.timestamps = [];
//};
/**
@ -434,7 +441,7 @@ StatsCollector.prototype.processStatsReport = function () {
try {
if (getStatValue(now, 'receiveBandwidth') ||
getStatValue(now, 'sendBandwidth')) {
PeerStats.bandwidth = {
this.conferenceStats.bandwidth = {
"download": Math.round(
(getStatValue(now, 'receiveBandwidth')) / 1000),
"upload": Math.round(
@ -457,18 +464,18 @@ StatsCollector.prototype.processStatsReport = function () {
if(!ip || !type || !localIP || active != "true")
continue;
var addressSaved = false;
for(var i = 0; i < PeerStats.transport.length; i++)
for(var i = 0; i < this.conferenceStats.transport.length; i++)
{
if(PeerStats.transport[i].ip == ip &&
PeerStats.transport[i].type == type &&
PeerStats.transport[i].localip == localIP)
if(this.conferenceStats.transport[i].ip == ip &&
this.conferenceStats.transport[i].type == type &&
this.conferenceStats.transport[i].localip == localIP)
{
addressSaved = true;
}
}
if(addressSaved)
continue;
PeerStats.transport.push({localip: localIP, ip: ip, type: type});
this.conferenceStats.transport.push({localip: localIP, ip: ip, type: type});
continue;
}
@ -479,7 +486,7 @@ StatsCollector.prototype.processStatsReport = function () {
var local = this.currentStatsReport[now.localCandidateId];
var remote = this.currentStatsReport[now.remoteCandidateId];
PeerStats.transport.push({localip: local.ipAddress + ":" + local.portNumber,
this.conferenceStats.transport.push({localip: local.ipAddress + ":" + local.portNumber,
ip: remote.ipAddress + ":" + remote.portNumber, type: local.transport});
}
@ -498,16 +505,11 @@ StatsCollector.prototype.processStatsReport = function () {
var ssrc = getStatValue(now, 'ssrc');
if(!ssrc)
continue;
var jid = APP.xmpp.getJidFromSSRC(ssrc);
if (!jid && (Date.now() - now.timestamp) < 3000) {
console.warn("No jid for ssrc: " + ssrc);
continue;
}
var jidStats = this.jid2stats[jid];
if (!jidStats) {
jidStats = new PeerStats();
this.jid2stats[jid] = jidStats;
var ssrcStats = this.ssrc2stats[ssrc];
if (!ssrcStats) {
ssrcStats = new PeerStats();
this.ssrc2stats[ssrc] = ssrcStats;
}
@ -544,7 +546,7 @@ StatsCollector.prototype.processStatsReport = function () {
lossRate = 0;
var packetsTotal = (packetRate + lossRate);
jidStats.setSsrcLoss(ssrc,
ssrcStats.setSsrcLoss(ssrc,
{"packetsTotal": packetsTotal,
"packetsLost": lossRate,
"isDownloadStream": isDownloadStream});
@ -582,7 +584,7 @@ StatsCollector.prototype.processStatsReport = function () {
bytesSent = Math.round(((bytesSent * 8) / time) / 1000);
}
jidStats.setSsrcBitrate(ssrc, {
ssrcStats.setSsrcBitrate(ssrc, {
"download": bytesReceived,
"upload": bytesSent});
@ -603,11 +605,11 @@ StatsCollector.prototype.processStatsReport = function () {
if(resolution.height && resolution.width)
{
jidStats.setSsrcResolution(ssrc, resolution);
ssrcStats.setSsrcResolution(ssrc, resolution);
}
else
{
jidStats.setSsrcResolution(ssrc, null);
ssrcStats.setSsrcResolution(ssrc, null);
}
@ -620,38 +622,38 @@ StatsCollector.prototype.processStatsReport = function () {
var bitrateDownload = 0;
var bitrateUpload = 0;
var resolutions = {};
Object.keys(this.jid2stats).forEach(
Object.keys(this.ssrc2stats).forEach(
function (jid)
{
Object.keys(self.jid2stats[jid].ssrc2Loss).forEach(
Object.keys(self.ssrc2stats[jid].ssrc2Loss).forEach(
function (ssrc)
{
var type = "upload";
if(self.jid2stats[jid].ssrc2Loss[ssrc].isDownloadStream)
if(self.ssrc2stats[jid].ssrc2Loss[ssrc].isDownloadStream)
type = "download";
totalPackets[type] +=
self.jid2stats[jid].ssrc2Loss[ssrc].packetsTotal;
self.ssrc2stats[jid].ssrc2Loss[ssrc].packetsTotal;
lostPackets[type] +=
self.jid2stats[jid].ssrc2Loss[ssrc].packetsLost;
self.ssrc2stats[jid].ssrc2Loss[ssrc].packetsLost;
}
);
Object.keys(self.jid2stats[jid].ssrc2bitrate).forEach(
Object.keys(self.ssrc2stats[jid].ssrc2bitrate).forEach(
function (ssrc) {
bitrateDownload +=
self.jid2stats[jid].ssrc2bitrate[ssrc].download;
self.ssrc2stats[jid].ssrc2bitrate[ssrc].download;
bitrateUpload +=
self.jid2stats[jid].ssrc2bitrate[ssrc].upload;
self.ssrc2stats[jid].ssrc2bitrate[ssrc].upload;
delete self.jid2stats[jid].ssrc2bitrate[ssrc];
delete self.ssrc2stats[jid].ssrc2bitrate[ssrc];
}
);
resolutions[jid] = self.jid2stats[jid].ssrc2resolution;
resolutions[jid] = self.ssrc2stats[jid].ssrc2resolution;
}
);
PeerStats.bitrate = {"upload": bitrateUpload, "download": bitrateDownload};
this.conferenceStats.bitrate = {"upload": bitrateUpload, "download": bitrateDownload};
PeerStats.packetLoss = {
this.conferenceStats.packetLoss = {
total:
calculatePacketLoss(lostPackets.download + lostPackets.upload,
totalPackets.download + totalPackets.upload),
@ -662,13 +664,13 @@ StatsCollector.prototype.processStatsReport = function () {
};
this.eventEmitter.emit("statistics.connectionstats",
{
"bitrate": PeerStats.bitrate,
"packetLoss": PeerStats.packetLoss,
"bandwidth": PeerStats.bandwidth,
"bitrate": this.conferenceStats.bitrate,
"packetLoss": this.conferenceStats.packetLoss,
"bandwidth": this.conferenceStats.bandwidth,
"resolution": resolutions,
"transport": PeerStats.transport
"transport": this.conferenceStats.transport
});
PeerStats.transport = [];
this.conferenceStats.transport = [];
};
@ -683,7 +685,8 @@ StatsCollector.prototype.processAudioLevelReport = function () {
for (var idx in this.currentAudioLevelsReport) {
var now = this.currentAudioLevelsReport[idx];
if (now.type != 'ssrc') {
//if we don't have "packetsReceived" this is local stream
if (now.type != 'ssrc' || !getStatValue(now, 'packetsReceived')) {
continue;
}
@ -694,17 +697,16 @@ StatsCollector.prototype.processAudioLevelReport = function () {
}
var ssrc = getStatValue(now, 'ssrc');
var jid = APP.xmpp.getJidFromSSRC(ssrc);
if (!jid) {
if (!ssrc) {
if((Date.now() - now.timestamp) < 3000)
console.warn("No jid for ssrc: " + ssrc);
console.warn("No ssrc: ");
continue;
}
var jidStats = this.jid2stats[jid];
if (!jidStats) {
jidStats = new PeerStats();
this.jid2stats[jid] = jidStats;
var ssrcStats = this.ssrc2stats[ssrc];
if (!ssrcStats) {
ssrcStats = new PeerStats();
this.ssrc2stats[ssrc] = ssrcStats;
}
// Audio level
@ -725,9 +727,8 @@ StatsCollector.prototype.processAudioLevelReport = function () {
// TODO: can't find specs about what this value really is,
// but it seems to vary between 0 and around 32k.
audioLevel = audioLevel / 32767;
jidStats.setSsrcAudioLevel(ssrc, audioLevel);
if(jid != APP.xmpp.myJid())
this.eventEmitter.emit("statistics.audioLevel", jid, audioLevel);
ssrcStats.setSsrcAudioLevel(ssrc, audioLevel);
this.eventEmitter.emit("statistics.audioLevel", ssrc, audioLevel);
}
}
};

View File

@ -1,143 +1,146 @@
/* global require, APP */
/**
* Created by hristo on 8/4/14.
*/
var LocalStats = require("./LocalStatsCollector.js");
var RTPStats = require("./RTPStatsCollector.js");
var EventEmitter = require("events");
var StreamEventTypes = require("../../service/RTC/StreamEventTypes.js");
var XMPPEvents = require("../../service/xmpp/XMPPEvents");
var CallStats = require("./CallStats");
var RTCEvents = require("../../service/RTC/RTCEvents");
//var StreamEventTypes = require("../../service/RTC/StreamEventTypes.js");
//var XMPPEvents = require("../../service/xmpp/XMPPEvents");
//var CallStats = require("./CallStats");
//var RTCEvents = require("../../service/RTC/RTCEvents");
var eventEmitter = new EventEmitter();
//
//function onDisposeConference(onUnload) {
// CallStats.sendTerminateEvent();
// stopRemote();
// if(onUnload) {
// stopLocal();
// eventEmitter.removeAllListeners();
// }
//}
var localStats = null;
var rtpStats = null;
function stopLocal() {
if (localStats) {
localStats.stop();
localStats = null;
}
function Statistics() {
this.localStats = null;
this.rtpStats = null;
this.eventEmitter = new EventEmitter();
}
function stopRemote() {
if (rtpStats) {
rtpStats.stop();
eventEmitter.emit("statistics.stop");
rtpStats = null;
}
}
function startRemoteStats (peerconnection) {
if (rtpStats) {
rtpStats.stop();
Statistics.prototype.startRemoteStats = function (peerconnection) {
if (this.rtpStats) {
this.rtpStats.stop();
}
rtpStats = new RTPStats(peerconnection, 200, 2000, eventEmitter);
rtpStats.start();
this.rtpStats = new RTPStats(peerconnection, 200, 2000, this.eventEmitter);
this.rtpStats.start();
}
function onStreamCreated(stream) {
if(stream.getOriginalStream().getAudioTracks().length === 0) {
Statistics.prototype.startLocalStats = function (stream) {
if(stream.getType() !== "audio")
return;
}
localStats = new LocalStats(stream.getOriginalStream(), 200, statistics,
eventEmitter);
localStats.start();
this.localStats = new LocalStats(stream.getOriginalStream(), 200, this,
this.eventEmitter);
this.localStats.start();
}
function onDisposeConference(onUnload) {
CallStats.sendTerminateEvent();
stopRemote();
if(onUnload) {
stopLocal();
eventEmitter.removeAllListeners();
Statistics.prototype.addAudioLevelListener = function(listener)
{
this.eventEmitter.on("statistics.audioLevel", listener);
}
Statistics.prototype.removeAudioLevelListener = function(listener)
{
this.eventEmitter.removeListener("statistics.audioLevel", listener);
}
Statistics.prototype.dispose = function () {
this.stopLocal();
this.stopRemote();
if(this.eventEmitter)
{
this.eventEmitter.removeAllListeners();
}
}
var statistics = {
/**
* Indicates that this audio level is for local jid.
* @type {string}
*/
LOCAL_JID: 'local',
addAudioLevelListener: function(listener)
{
eventEmitter.on("statistics.audioLevel", listener);
},
removeAudioLevelListener: function(listener)
{
eventEmitter.removeListener("statistics.audioLevel", listener);
},
addConnectionStatsListener: function(listener)
{
eventEmitter.on("statistics.connectionstats", listener);
},
removeConnectionStatsListener: function(listener)
{
eventEmitter.removeListener("statistics.connectionstats", listener);
},
addRemoteStatsStopListener: function(listener)
{
eventEmitter.on("statistics.stop", listener);
},
removeRemoteStatsStopListener: function(listener)
{
eventEmitter.removeListener("statistics.stop", listener);
},
stop: function () {
stopLocal();
stopRemote();
if(eventEmitter)
{
eventEmitter.removeAllListeners();
}
},
stopRemoteStatistics: function()
{
stopRemote();
},
start: function () {
APP.RTC.addStreamListener(onStreamCreated,
StreamEventTypes.EVENT_TYPE_LOCAL_CREATED);
APP.xmpp.addListener(XMPPEvents.DISPOSE_CONFERENCE, onDisposeConference);
//FIXME: we may want to change CALL INCOMING event to onnegotiationneeded
APP.xmpp.addListener(XMPPEvents.CALL_INCOMING, function (event) {
startRemoteStats(event.peerconnection);
// CallStats.init(event);
});
APP.xmpp.addListener(XMPPEvents.PEERCONNECTION_READY, function (session) {
CallStats.init(session);
});
//FIXME: that event is changed to TRACK_MUTE_CHANGED
// APP.RTC.addListener(RTCEvents.AUDIO_MUTE, function (mute) {
// CallStats.sendMuteEvent(mute, "audio");
// });
APP.xmpp.addListener(XMPPEvents.CONFERENCE_SETUP_FAILED, function () {
CallStats.sendSetupFailedEvent();
});
//FIXME: that event is changed to TRACK_MUTE_CHANGED
// APP.RTC.addListener(RTCEvents.VIDEO_MUTE, function (mute) {
// CallStats.sendMuteEvent(mute, "video");
// });
Statistics.prototype.stopLocal = function () {
if (this.localStats) {
this.localStats.stop();
this.localStats = null;
}
};
}
Statistics.prototype.stopRemote = function () {
if (this.rtpStats) {
this.rtpStats.stop();
this.eventEmitter.emit("statistics.stop");
this.rtpStats = null;
}
}
Statistics.LOCAL_JID = require("../../service/statistics/constants").LOCAL_JID;
//
//var statistics = {
// /**
// * Indicates that this audio level is for local jid.
// * @type {string}
// */
// LOCAL_JID: 'local',
//
// addConnectionStatsListener: function(listener)
// {
// eventEmitter.on("statistics.connectionstats", listener);
// },
//
// removeConnectionStatsListener: function(listener)
// {
// eventEmitter.removeListener("statistics.connectionstats", listener);
// },
//
//
// addRemoteStatsStopListener: function(listener)
// {
// eventEmitter.on("statistics.stop", listener);
// },
//
// removeRemoteStatsStopListener: function(listener)
// {
// eventEmitter.removeListener("statistics.stop", listener);
// },
//
//
// stopRemoteStatistics: function()
// {
// stopRemote();
// },
//
//// Already implemented with the constructor
// start: function () {
// APP.RTC.addStreamListener(onStreamCreated,
// StreamEventTypes.EVENT_TYPE_LOCAL_CREATED);
// APP.xmpp.addListener(XMPPEvents.DISPOSE_CONFERENCE, onDisposeConference);
// //FIXME: we may want to change CALL INCOMING event to onnegotiationneeded
// APP.xmpp.addListener(XMPPEvents.CALL_INCOMING, function (event) {
// startRemoteStats(event.peerconnection);
//// CallStats.init(event);
// });
//// APP.xmpp.addListener(XMPPEvents.PEERCONNECTION_READY, function (session) {
//// CallStats.init(session);
//// });
// //FIXME: that event is changed to TRACK_MUTE_CHANGED
//// APP.RTC.addListener(RTCEvents.AUDIO_MUTE, function (mute) {
//// CallStats.sendMuteEvent(mute, "audio");
//// });
//// APP.xmpp.addListener(XMPPEvents.CONFERENCE_SETUP_FAILED, function () {
//// CallStats.sendSetupFailedEvent();
//// });
// //FIXME: that event is changed to TRACK_MUTE_CHANGED
//// APP.RTC.addListener(RTCEvents.VIDEO_MUTE, function (mute) {
//// CallStats.sendMuteEvent(mute, "video");
//// });
// }
//};
module.exports = statistics;
module.exports = Statistics;

View File

@ -607,4 +607,10 @@ ChatRoom.prototype.addLocalStreams = function (localStreams) {
this.session.addLocalStreams(localStreams);
}
ChatRoom.prototype.getJidBySSRC = function (ssrc) {
if (!this.session)
return null;
return this.session.getSsrcOwner(ssrc);
};
module.exports = ChatRoom;

View File

@ -267,17 +267,10 @@ XMPP.prototype.eject = function (jid) {
this.connection.moderate.eject(jid);
};
XMPP.prototype.getJidFromSSRC = function (ssrc) {
if (!this.isConferenceInProgress())
return null;
return this.connection.jingle.activecall.getSsrcOwner(ssrc);
};
XMPP.prototype.getSessions = function () {
return this.connection.jingle.sessions;
};
XMPP.prototype.disconnect = function () {
if (this.disconnectInProgress || !this.connection || !this.connection.connected)
{

View File

@ -0,0 +1,4 @@
var Constants = {
LOCAL_JID: 'local'
};
module.exports = Constants;