- /**
- * Function that sends the stats to the API server.
- * @method _postStatsToServer
- * @private
- * @for Skylink
- * @since 0.6.31
- */
- Skylink.prototype._postStats = function (endpoint, params) {
- var self = this;
- var requestBody = {};
- if(self._initOptions.enableStatsGathering){
- if(Array.isArray(params)){
- requestBody.data = params;
- }
- else{
- requestBody = params;
- }
- requestBody.client_id = ((self._user && self._user.uid) || 'dummy') + '_' + self._statIdRandom;
- requestBody.app_key = self._initOptions.appKey;
- requestBody.timestamp = (new Date()).toISOString();
-
- // Simply post the data directly to the API server without caring if it is successful or not.
- try {
- var xhr = new XMLHttpRequest();
- xhr.onerror = function () { };
- xhr.open('POST', self._initOptions.statsServer + endpoint, true);
- xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8');
- xhr.send(JSON.stringify(requestBody));
-
- } catch (error) {
- log.error([null, 'XMLHttpRequest', "POST", 'Error in posting stats data ->'], error);
- }
- }
- };
-
- /**
- * Function that handles the posting of client information.
- * @method _handleClientStats
- * @private
- * @for Skylink
- * @since 0.6.31
- */
- Skylink.prototype._handleClientStats = function() {
- var self = this;
- var statsObject = {
- username: (self._user && self._user.uid) || null,
- sdk_name: 'web',
- sdk_version: self.VERSION,
- agent_name: AdapterJS.webrtcDetectedBrowser,
- agent_version: AdapterJS.webrtcDetectedVersion,
- agent_platform: navigator.platform,
- agent_plugin_version: (AdapterJS.WebRTCPlugin.plugin && AdapterJS.WebRTCPlugin.plugin.VERSION) || null
- };
-
- self._postStats('/rest/stats/client', statsObject);
- };
-
- /**
- * Function that handles the posting of session states.
- * @method _handleSessionStats
- * @private
- * @for Skylink
- * @since 0.6.31
- */
- Skylink.prototype._handleSessionStats = function(message) {
- var self = this;
- var statsObject = {
- room_id: self._room && self._room.id,
- user_id: (self._user && self._user.sid) || null,
- state: message.type,
- contents: message
- };
-
- self._postStats('/rest/stats/session', statsObject);
- };
-
- /**
- * Function that handles the posting of app key authentication states.
- * @method _handleAuthStats
- * @private
- * @for Skylink
- * @since 0.6.31
- */
- Skylink.prototype._handleAuthStats = function(state, result, status, error) {
- var self = this;
- var statsObject = {
- room_id: (result && result.room_key) || null,
- state: state,
- http_status: status,
- http_error: (typeof error === 'string' ? error : (error && error.message)) || null,
- api_url: self._path,
- api_result: result
- };
-
- self._postStats('/rest/stats/auth', statsObject);
- };
-
- /**
- * Function that handles the posting of socket connection states.
- * @method _handleSignalingStats
- * @private
- * @for Skylink
- * @since 0.6.31
- */
- Skylink.prototype._handleSignalingStats = function(state, retries, error) {
- var self = this;
- var socketSession = clone(self._socketSession);
- var statsObject = {
- room_id: self._room && self._room.id,
- user_id: (self._user && self._user.sid) || null,
- state: state,
- signaling_url: socketSession.socketServer,
- signaling_transport: socketSession.transportType.toLowerCase(),
- // Use the retries from the function itself to prevent non-sequential event calls issues.
- attempts: retries,
- error: (typeof error === 'string' ? error : (error && error.message)) || null
- };
-
- self._postStats('/rest/stats/client/signaling', statsObject);
- };
-
- /**
- * Function that handles the posting of peer ICE connection states.
- * @method _handleIceConnectionStats
- * @private
- * @for Skylink
- * @since 0.6.31
- */
- Skylink.prototype._handleIceConnectionStats = function(state, peerId) {
- var self = this;
- var statsObject = {
- room_id: self._room && self._room.id,
- user_id: self._user && self._user.sid,
- peer_id: peerId,
- state: state,
- local_candidate: {},
- remote_candidate: {}
- };
- // Set a timeout to pause process to ensure the stats retrieval does not run at the same time
- // when the state is triggered, so that the selected ICE candidate pair information can be returned.
- self._retrieveStats(peerId, function (error, stats) {
- if (stats) {
- // Parse the selected ICE candidate pair for both local and remote candidate.
- ['local', 'remote'].forEach(function (dirType) {
- var candidate = stats.selectedCandidate[dirType];
-
- if (candidate) {
- statsObject[dirType + '_candidate'].ip_address = candidate.ipAddress || null;
- statsObject[dirType + '_candidate'].port_number = candidate.portNumber || null;
- statsObject[dirType + '_candidate'].candidate_type = candidate.candidateType || null;
- statsObject[dirType + '_candidate'].protocol = candidate.transport || null;
- statsObject[dirType + '_candidate'].priority = candidate.priority || null;
-
- // This is only available for the local ICE candidate.
- if (dirType === 'local') {
- statsObject.local_candidate.network_type = candidate.networkType || null;
- }
- }
- });
- }
- self._postStats('/rest/stats/client/iceconnection', statsObject);
-
- }, true);
- };
-
- /**
- * Function that handles the posting of peer local/remote ICE candidate processing states.
- * @method _handleIceCandidateStats
- * @private
- * @for Skylink
- * @since 0.6.31
- */
- Skylink.prototype._handleIceCandidateStats = function(state, peerId, candidateId, candidate, error) {
- var self = this;
- var statsObject = {
- room_id: self._room && self._room.id,
- user_id: self._user && self._user.sid,
- peer_id: peerId,
- state: state,
- is_remote: !!candidateId,
- candidate_id: candidateId || null,
- candidate_sdp_mid: candidate.sdpMid,
- candidate_sdp_mindex: candidate.sdpMLineIndex,
- candidate_candidate: candidate.candidate,
- error: (typeof error === 'string' ? error : (error && error.message)) || null,
- };
- self._manageStatsBuffer('iceCandidate', statsObject, '/rest/stats/client/icecandidate');
- };
-
- /**
- * Function that handles the posting of peer local/remote ICE gathering states.
- * @method _handleIceGatheringStats
- * @private
- * @for Skylink
- * @since 0.6.31
- */
- Skylink.prototype._handleIceGatheringStats = function(state, peerId, isRemote) {
- var self = this;
- var statsObject = {
- room_id: self._room && self._room.id,
- user_id: self._user && self._user.sid,
- peer_id: peerId,
- state: state,
- is_remote: isRemote
- };
- self._manageStatsBuffer('iceGathering', statsObject, '/rest/stats/client/icegathering');
- };
-
- /**
- * Function that handles the posting of peer connection negotiation states.
- * @method _handleNegotiationStats
- * @private
- * @for Skylink
- * @since 0.6.31
- */
- Skylink.prototype._handleNegotiationStats = function(state, peerId, sdpOrMessage, isRemote, error) {
- var self = this;
- var statsObject = {
- room_id: self._room && self._room.id,
- user_id: self._user && self._user.sid,
- peer_id: peerId,
- state: state,
- is_remote: isRemote,
- // Currently sharing a parameter "sdpOrMessage" that indicates a "welcome" message
- // or session description to save parameters length.
- weight: sdpOrMessage.weight,
- sdp_type: null,
- sdp_sdp: null,
- error: (typeof error === 'string' ? error : (error && error.message)) || null,
- };
-
- // Retrieve the weight for states where the "weight" field is not available.
- if (['enter', 'welcome', 'restart'].indexOf(state) === -1) {
- // Retrieve the peer's weight if it from remote end.
- statsObject.weight = self.getPeerInfo(isRemote ? peerId : undefined).config.priorityWeight;
- statsObject.sdp_type = (sdpOrMessage && sdpOrMessage.type) || null;
- statsObject.sdp_sdp = (sdpOrMessage && sdpOrMessage.sdp) || null;
- }
- self._manageStatsBuffer('negotiation', statsObject, '/rest/stats/client/negotiation');
- };
-
- /**
- * Function that handles the posting of peer connection bandwidth information.
- * @method _handleBandwidthStats
- * @private
- * @for Skylink
- * @since 0.6.31
- */
- Skylink.prototype._handleBandwidthStats = function (peerId) {
- var self = this;
- var statsObject = {
- room_id: self._room && self._room.id,
- user_id: self._user && self._user.sid,
- peer_id: peerId,
- audio_send: { tracks: [] },
- audio_recv: {},
- video_send: { tracks: [] },
- video_recv: {}
- };
-
- var useStream = self._streams.screenshare || self._streams.userMedia || null;
- var mutedStatus = self.getPeerInfo().mediaStatus;
-
- // When stream is available, format the stream tracks information.
- // The SDK currently only allows sending of 1 stream at a time that has only 1 audio and video track each.
- if (useStream) {
- // Parse the audio track if it exists only.
- if (useStream.tracks.audio) {
- statsObject.audio_send.tracks = [{
- stream_id: useStream.id,
- id: useStream.tracks.audio.id,
- label: useStream.tracks.audio.label,
- muted: mutedStatus.audioMuted
- }];
- }
-
- // Parse the video track if it exists only.
- if (useStream.tracks.video) {
- statsObject.video_send.tracks = [{
- stream_id: useStream.id,
- id: useStream.tracks.video.id,
- label: useStream.tracks.video.label,
- height: useStream.tracks.video.height,
- width: useStream.tracks.video.width,
- muted: mutedStatus.videoMuted
- }];
- }
- }
-
- self._retrieveStats(peerId, function (error, stats) {
- if (error) {
- statsObject.error = error && error.message;
- stats = {
- audio: { sending: {}, receiving: {} },
- video: { sending: {}, receiving: {} }
- };
- }
-
- // Common function to parse and handle any `null`/`undefined` values.
- var formatValue = function (mediaType, directionType, itemKey) {
- var value = stats[mediaType][directionType === 'send' ? 'sending' : 'receiving'][itemKey];
- if (['number', 'string', 'boolean'].indexOf(typeof value) > -1) {
- return value;
- }
- return null;
- };
-
- // Parse bandwidth information for sending audio packets.
- statsObject.audio_send.bytes = formatValue('audio', 'send', 'bytes');
- statsObject.audio_send.packets = formatValue('audio', 'send', 'packets');
- statsObject.audio_send.round_trip_time = formatValue('audio', 'send', 'rtt');
- statsObject.audio_send.nack_count = formatValue('audio', 'send', 'nacks');
- statsObject.audio_send.echo_return_loss = formatValue('audio', 'send', 'echoReturnLoss');
- statsObject.audio_send.echo_return_loss_enhancement = formatValue('audio', 'send', 'echoReturnLossEnhancement');
-
- // Parse bandwidth information for receiving audio packets.
- statsObject.audio_recv.bytes = formatValue('audio', 'recv', 'bytes');
- statsObject.audio_recv.packets = formatValue('audio', 'recv', 'packets');
- statsObject.audio_recv.packets_lost = formatValue('audio', 'recv', 'packetsLost');
- statsObject.video_recv.packets_discarded = formatValue('audio', 'recv', 'packetsDiscarded');
- statsObject.audio_recv.jitter = formatValue('audio', 'recv', 'jitter');
- statsObject.audio_recv.nack_count = formatValue('audio', 'recv', 'nacks');
-
- // Parse bandwidth information for sending video packets.
- statsObject.video_send.bytes = formatValue('video', 'send', 'bytes');
- statsObject.video_send.packets = formatValue('video', 'send', 'packets');
- statsObject.video_send.round_trip_time = formatValue('video', 'send', 'rtt');
- statsObject.video_send.nack_count = formatValue('video', 'send', 'nacks');
- statsObject.video_send.firs_count = formatValue('video', 'send', 'firs');
- statsObject.video_send.plis_count = formatValue('video', 'send', 'plis');
- statsObject.video_send.frames = formatValue('video', 'send', 'frames');
- statsObject.video_send.frames_encoded = formatValue('video', 'send', 'framesEncoded');
- statsObject.video_send.frames_dropped = formatValue('video', 'send', 'framesDropped');
- statsObject.video_send.frame_width = formatValue('video', 'send', 'frameWidth');
- statsObject.video_send.frame_height = formatValue('video', 'send', 'frameHeight');
- statsObject.video_send.framerate = formatValue('video', 'send', 'frameRate');
- statsObject.video_send.framerate_input = formatValue('video', 'send', 'frameRateInput');
- statsObject.video_send.framerate_encoded = formatValue('video', 'send', 'frameRateEncoded');
- statsObject.video_send.framerate_mean = formatValue('video', 'send', 'frameRateMean');
- statsObject.video_send.framerate_std_dev = formatValue('video', 'send', 'frameRateStdDev');
- statsObject.video_send.cpu_limited_resolution = formatValue('video', 'send', 'cpuLimitedResolution');
- statsObject.video_send.bandwidth_limited_resolution = formatValue('video', 'send', 'bandwidthLimitedResolution');
-
- // Parse bandwidth information for receiving video packets.
- statsObject.video_recv.bytes = formatValue('video', 'recv', 'bytes');
- statsObject.video_recv.packets = formatValue('video', 'recv', 'packets');
- statsObject.video_recv.packets_lost = formatValue('video', 'recv', 'packetsLost');
- statsObject.video_recv.packets_discarded = formatValue('video', 'recv', 'packetsDiscarded');
- statsObject.video_recv.jitter = formatValue('video', 'recv', 'jitter');
- statsObject.video_recv.nack_count = formatValue('video', 'recv', 'nacks');
- statsObject.video_recv.firs_count = formatValue('video', 'recv', 'firs');
- statsObject.video_recv.plis_count = formatValue('video', 'recv', 'plis');
- statsObject.video_recv.frames = formatValue('video', 'recv', 'frames');
- statsObject.video_recv.frames_decoded = formatValue('video', 'recv', 'framesDecoded');
- statsObject.video_recv.frame_width = formatValue('video', 'recv', 'frameWidth');
- statsObject.video_recv.frame_height = formatValue('video', 'recv', 'frameHeight');
- statsObject.video_recv.framerate = formatValue('video', 'recv', 'frameRate');
- statsObject.video_recv.framerate_output = formatValue('video', 'recv', 'frameRateOutput');
- statsObject.video_recv.framerate_decoded = formatValue('video', 'recv', 'frameRateDecoded');
- statsObject.video_recv.framerate_mean = formatValue('video', 'recv', 'frameRateMean');
- statsObject.video_recv.framerate_std_dev = formatValue('video', 'recv', 'frameRateStdDev');
- statsObject.video_recv.qp_sum = formatValue('video', 'recv', 'qpSum');
- self._postStats('/rest/stats/client/bandwidth', statsObject);
- }, true);
- };
-
- /**
- * Function that handles the posting of recording states.
- * @method _handleRecordingStats
- * @private
- * @for Skylink
- * @since 0.6.31
- */
- Skylink.prototype._handleRecordingStats = function(state, recordingId, recordings, error) {
- var self = this;
- var statsObject = {
- room_id: self._room && self._room.id,
- user_id: self._user && self._user.sid,
- state: state,
- recording_id: recordingId || null,
- recordings: recordings,
- error: (typeof error === 'string' ? error : (error && error.message)) || null
- };
-
- self._postStats('/rest/stats/client/recording', statsObject);
- };
-
- /**
- * Function that handles the posting of datachannel states.
- * @method _handleDatachannelStats
- * @private
- * @for Skylink
- * @since 0.6.31
- */
- Skylink.prototype._handleDatachannelStats = function(state, peerId, channel, channelProp, error) {
- var self = this;
- var statsObject = {
- room_id: self._room && self._room.id,
- user_id: self._user && self._user.sid,
- peer_id: peerId,
- state: state,
- channel_id: channel && channel.id,
- channel_label: channel && channel.label,
- channel_type: channelProp === 'main' ? 'persistent' : 'temporal',
- channel_binary_type: channel && channel.binaryType,
- error: (typeof error === 'string' ? error : (error && error.message)) || null
- };
-
- if (channel && AdapterJS.webrtcDetectedType === 'plugin') {
- statsObject.channel_binary_type = 'int8Array';
-
- // For IE 10 and below browsers, binary support is not available.
- if (AdapterJS.webrtcDetectedBrowser === 'IE' && AdapterJS.webrtcDetectedVersion < 11) {
- statsObject.channel_binary_type = 'none';
- }
- }
-
- self._postStats('/rest/stats/client/datachannel', statsObject);
- };
-
- Skylink.prototype._stats_buffer = {};
- /**
- * Function that handles buffer of stats data
- * @method _handleDatachannelStats
- * @private
- * @for Skylink
- * @since 0.6.35
- */
- Skylink.prototype._manageStatsBuffer = function(operation, data, url){
- var self = this;
- if(self._stats_buffer[operation] === undefined){
- self._stats_buffer[operation] = {};
- self._stats_buffer[operation].url = url;
- self._stats_buffer[operation].data = [];
- }
- self._stats_buffer[operation].data.push(data);
- setInterval(function () {
- for (var key in self._stats_buffer) {
- if (self._stats_buffer[key]["data"].length > 0) {
- self._postStats(self._stats_buffer[key]["url"], self._stats_buffer[key]["data"]);
- self._stats_buffer[key]["data"] = [];
- }
- }
- }, 5000);
- };
-
-