2019-11-30 05:48:04 +08:00
|
|
|
import logger from '/imports/startup/client/logger';
|
|
|
|
|
2020-01-28 21:07:21 +08:00
|
|
|
const STATS = Meteor.settings.public.stats;
|
|
|
|
|
2021-04-24 06:15:32 +08:00
|
|
|
// Probes done in an interval
|
|
|
|
const PROBES = 5;
|
|
|
|
const INTERVAL = STATS.interval / PROBES;
|
2019-11-30 05:48:04 +08:00
|
|
|
|
2019-11-30 19:29:51 +08:00
|
|
|
const stop = callback => {
|
2020-01-30 01:52:55 +08:00
|
|
|
logger.debug(
|
|
|
|
{ logCode: 'stats_stop_monitor' },
|
2019-11-30 19:29:51 +08:00
|
|
|
'Lost peer connection. Stopping monitor'
|
|
|
|
);
|
|
|
|
callback(clearResult());
|
|
|
|
return;
|
|
|
|
};
|
|
|
|
|
2020-04-29 02:51:35 +08:00
|
|
|
const isActive = conn => {
|
|
|
|
let active = false;
|
|
|
|
|
|
|
|
if (conn) {
|
|
|
|
const { connectionState } = conn;
|
|
|
|
const logCode = 'stats_connection_state';
|
|
|
|
|
|
|
|
switch (connectionState) {
|
|
|
|
case 'new':
|
|
|
|
case 'connecting':
|
|
|
|
case 'connected':
|
|
|
|
case 'disconnected':
|
|
|
|
active = true;
|
|
|
|
break;
|
|
|
|
case 'failed':
|
|
|
|
case 'closed':
|
|
|
|
default:
|
|
|
|
logger.warn({ logCode }, connectionState);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
logger.error(
|
|
|
|
{ logCode: 'stats_missing_connection' },
|
|
|
|
'Missing connection'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return active;
|
2020-02-15 05:14:21 +08:00
|
|
|
};
|
|
|
|
|
2019-11-30 05:48:04 +08:00
|
|
|
const collect = (conn, callback) => {
|
|
|
|
let stats = [];
|
|
|
|
|
2021-04-24 06:15:32 +08:00
|
|
|
const monitor = (conn, stats) => {
|
2020-04-29 02:51:35 +08:00
|
|
|
if (!isActive(conn)) return stop(callback);
|
2019-11-30 05:48:04 +08:00
|
|
|
|
|
|
|
conn.getStats().then(results => {
|
2019-11-30 19:29:51 +08:00
|
|
|
if (!results) return stop(callback);
|
2019-11-30 05:48:04 +08:00
|
|
|
|
|
|
|
let inboundRTP;
|
|
|
|
let remoteInboundRTP;
|
|
|
|
|
|
|
|
results.forEach(res => {
|
|
|
|
switch (res.type) {
|
|
|
|
case 'inbound-rtp':
|
|
|
|
inboundRTP = res;
|
|
|
|
break;
|
|
|
|
case 'remote-inbound-rtp':
|
|
|
|
remoteInboundRTP = res;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
if (inboundRTP || remoteInboundRTP) {
|
|
|
|
if (!inboundRTP) {
|
2020-01-30 01:52:55 +08:00
|
|
|
logger.debug(
|
|
|
|
{ logCode: 'stats_missing_inbound_rtc' },
|
2019-11-30 05:48:04 +08:00
|
|
|
'Missing local inbound RTC. Using remote instead'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
stats.push(buildData(inboundRTP || remoteInboundRTP));
|
2021-04-24 06:15:32 +08:00
|
|
|
while (stats.length > PROBES) stats.shift();
|
2019-11-30 05:48:04 +08:00
|
|
|
|
|
|
|
const interval = calculateInterval(stats);
|
2021-04-24 06:15:32 +08:00
|
|
|
callback(buildResult(interval));
|
2019-11-30 05:48:04 +08:00
|
|
|
}
|
|
|
|
|
2021-04-24 06:15:32 +08:00
|
|
|
setTimeout(monitor, INTERVAL, conn, stats);
|
2020-01-30 01:52:55 +08:00
|
|
|
}).catch(error => {
|
|
|
|
logger.debug(
|
|
|
|
{
|
|
|
|
logCode: 'stats_get_stats_error',
|
|
|
|
extraInfo: { error }
|
|
|
|
},
|
|
|
|
'WebRTC stats not available'
|
|
|
|
);
|
|
|
|
});
|
2019-11-30 05:48:04 +08:00
|
|
|
};
|
2021-05-10 01:26:14 +08:00
|
|
|
monitor(conn, stats);
|
2019-11-30 05:48:04 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
const buildData = inboundRTP => {
|
|
|
|
return {
|
|
|
|
packets: {
|
|
|
|
received: inboundRTP.packetsReceived,
|
|
|
|
lost: inboundRTP.packetsLost
|
|
|
|
},
|
|
|
|
bytes: {
|
|
|
|
received: inboundRTP.bytesReceived
|
|
|
|
},
|
|
|
|
jitter: inboundRTP.jitter
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2021-04-24 06:15:32 +08:00
|
|
|
const buildResult = (interval) => {
|
2019-11-30 05:48:04 +08:00
|
|
|
const rate = calculateRate(interval.packets);
|
|
|
|
return {
|
|
|
|
packets: {
|
|
|
|
received: interval.packets.received,
|
|
|
|
lost: interval.packets.lost
|
|
|
|
},
|
|
|
|
bytes: {
|
|
|
|
received: interval.bytes.received
|
|
|
|
},
|
|
|
|
jitter: interval.jitter,
|
|
|
|
rate: rate,
|
|
|
|
loss: calculateLoss(rate),
|
|
|
|
MOS: calculateMOS(rate)
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
const clearResult = () => {
|
2019-11-30 19:29:51 +08:00
|
|
|
return {
|
|
|
|
packets: {
|
|
|
|
received: 0,
|
|
|
|
lost: 0
|
|
|
|
},
|
|
|
|
bytes: {
|
|
|
|
received: 0
|
|
|
|
},
|
|
|
|
jitter: 0,
|
|
|
|
rate: 0,
|
|
|
|
loss: 0,
|
|
|
|
MOS: 0
|
|
|
|
};
|
2019-11-30 05:48:04 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
const diff = (single, first, last) => Math.abs((single ? 0 : last) - first);
|
|
|
|
|
|
|
|
const calculateInterval = (stats) => {
|
|
|
|
const single = stats.length === 1;
|
|
|
|
const first = stats[0];
|
|
|
|
const last = stats[stats.length - 1];
|
|
|
|
return {
|
|
|
|
packets: {
|
|
|
|
received: diff(single, first.packets.received, last.packets.received),
|
|
|
|
lost: diff(single, first.packets.lost, last.packets.lost)
|
|
|
|
},
|
|
|
|
bytes: {
|
|
|
|
received: diff(single, first.bytes.received, last.bytes.received)
|
|
|
|
},
|
2021-03-26 05:22:50 +08:00
|
|
|
jitter: Math.max.apply(Math, stats.map(s => s.jitter))
|
2019-11-30 05:48:04 +08:00
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
const calculateRate = (packets) => {
|
|
|
|
const { received, lost } = packets;
|
|
|
|
const rate = (received > 0) ? ((received - lost) / received) * 100 : 100;
|
|
|
|
if (rate < 0 || rate > 100) return 100;
|
|
|
|
return rate;
|
|
|
|
};
|
|
|
|
|
|
|
|
const calculateLoss = (rate) => {
|
|
|
|
return 1 - (rate / 100);
|
|
|
|
};
|
|
|
|
|
|
|
|
const calculateMOS = (rate) => {
|
|
|
|
return 1 + (0.035) * rate + (0.000007) * rate * (rate - 60) * (100 - rate);
|
|
|
|
};
|
|
|
|
|
2020-01-30 01:52:55 +08:00
|
|
|
const monitorAudioConnection = conn => {
|
|
|
|
if (!conn) return;
|
|
|
|
|
|
|
|
logger.debug(
|
|
|
|
{ logCode: 'stats_audio_monitor' },
|
2019-11-30 05:48:04 +08:00
|
|
|
'Starting to monitor audio connection'
|
|
|
|
);
|
|
|
|
|
|
|
|
collect(conn, (result) => {
|
|
|
|
const event = new CustomEvent('audiostats', { detail: result });
|
|
|
|
window.dispatchEvent(event);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2024-08-28 01:00:26 +08:00
|
|
|
/**
|
|
|
|
* Calculates the jitter buffer average.
|
|
|
|
* For more information see:
|
|
|
|
* https://www.w3.org/TR/webrtc-stats/#dom-rtcinboundrtpstreamstats-jitterbufferdelay
|
|
|
|
* @param {Object} inboundRtpData The RTCInboundRtpStreamStats object retrieved
|
|
|
|
* in getStats() call.
|
|
|
|
* @returns The jitter buffer average in ms
|
|
|
|
*/
|
|
|
|
const calculateJitterBufferAverage = (inboundRtpData) => {
|
|
|
|
if (!inboundRtpData) return 0;
|
|
|
|
|
|
|
|
const {
|
|
|
|
jitterBufferDelay,
|
|
|
|
jitterBufferEmittedCount,
|
|
|
|
} = inboundRtpData;
|
|
|
|
|
|
|
|
if (!jitterBufferDelay || !jitterBufferEmittedCount) return '--';
|
|
|
|
|
|
|
|
return Math.round((jitterBufferDelay / jitterBufferEmittedCount) * 1000);
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Given the data returned from getStats(), returns an array containing all the
|
|
|
|
* the stats of the given type.
|
|
|
|
* For more information see:
|
|
|
|
* https://developer.mozilla.org/en-US/docs/Web/API/RTCStatsReport
|
|
|
|
* and
|
|
|
|
* https://developer.mozilla.org/en-US/docs/Web/API/RTCStatsType
|
|
|
|
* @param {Object} data - RTCStatsReport object returned from getStats() API
|
|
|
|
* @param {String} type - The string type corresponding to RTCStatsType object
|
|
|
|
* @returns {Array[Object]} An array containing all occurrences of the given
|
|
|
|
* type in the data Object.
|
|
|
|
*/
|
|
|
|
const getDataType = (data, type) => {
|
|
|
|
if (!data || typeof data !== 'object' || !type) return [];
|
|
|
|
|
|
|
|
return Object.values(data).filter((stat) => stat.type === type);
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a new Object containing extra parameters calculated from inbound
|
|
|
|
* data. The input data is also appended in the returned Object.
|
|
|
|
* @param {Object} currentData - The object returned from getStats / service's
|
|
|
|
* getNetworkData()
|
|
|
|
* @returns {Object} the currentData object with the extra inbound network
|
|
|
|
* added to it.
|
|
|
|
*/
|
|
|
|
const addExtraInboundNetworkParameters = (data) => {
|
|
|
|
if (!data) return data;
|
|
|
|
|
|
|
|
const inboundRtpData = getDataType(data, 'inbound-rtp')[0];
|
|
|
|
|
|
|
|
if (!inboundRtpData) return data;
|
|
|
|
|
|
|
|
const extraParameters = {
|
|
|
|
jitterBufferAverage: calculateJitterBufferAverage(inboundRtpData),
|
|
|
|
packetsLost: inboundRtpData.packetsLost,
|
|
|
|
};
|
|
|
|
|
|
|
|
return Object.assign(inboundRtpData, extraParameters);
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the info about candidate-pair that is being used by the current peer.
|
|
|
|
* For firefox, or any other browser that doesn't support iceTransport
|
|
|
|
* property of RTCDtlsTransport, we retrieve the selected local candidate
|
|
|
|
* by looking into stats returned from getStats() api. For other browsers,
|
|
|
|
* we should use getSelectedCandidatePairFromPeer instead, because it has
|
|
|
|
* relatedAddress and relatedPort information about local candidate.
|
|
|
|
*
|
|
|
|
* @param {Object} stats object returned by getStats() api
|
|
|
|
* @returns An Object of type RTCIceCandidatePairStats containing information
|
|
|
|
* about the candidate-pair being used by the peer.
|
|
|
|
*
|
|
|
|
* For firefox, we can use the 'selected' flag to find the candidate pair
|
|
|
|
* being used, while in chrome we can retrieved the selected pair
|
|
|
|
* by looking for the corresponding transport of the active peer.
|
|
|
|
* For more information see:
|
|
|
|
* https://www.w3.org/TR/webrtc-stats/#dom-rtcicecandidatepairstats
|
|
|
|
* and
|
|
|
|
* https://developer.mozilla.org/en-US/docs/Web/API/RTCIceCandidatePairStats/selected#value
|
|
|
|
*/
|
|
|
|
const getSelectedCandidatePairFromStats = (stats) => {
|
|
|
|
if (!stats || typeof stats !== 'object') return null;
|
|
|
|
|
|
|
|
const transport = Object.values(stats).find((stat) => stat.type === 'transport') || {};
|
|
|
|
|
|
|
|
return Object.values(stats).find((stat) => stat.type === 'candidate-pair'
|
|
|
|
&& stat.nominated
|
|
|
|
&& (stat.selected || stat.id === transport.selectedCandidatePairId));
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the info about candidate-pair that is being used by the current peer.
|
|
|
|
* This function's return value (RTCIceCandidatePair object ) is different
|
|
|
|
* from getSelectedCandidatePairFromStats (RTCIceCandidatePairStats object).
|
|
|
|
* The information returned here contains the relatedAddress and relatedPort
|
|
|
|
* fields (only for candidates that are derived from another candidate, for
|
|
|
|
* host candidates, these fields are null). These field can be helpful for
|
|
|
|
* debugging network issues. For all the browsers that support iceTransport
|
|
|
|
* field of RTCDtlsTransport, we use this function as default to retrieve
|
|
|
|
* information about current selected-pair. For other browsers we retrieve it
|
|
|
|
* from getSelectedCandidatePairFromStats
|
|
|
|
*
|
|
|
|
* @returns {Object} An RTCIceCandidatePair represented the selected
|
|
|
|
* candidate-pair of the active peer.
|
|
|
|
*
|
|
|
|
* For more info see:
|
|
|
|
* https://www.w3.org/TR/webrtc/#dom-rtcicecandidatepair
|
|
|
|
* and
|
|
|
|
* https://developer.mozilla.org/en-US/docs/Web/API/RTCIceCandidatePair
|
|
|
|
* and
|
|
|
|
* https://developer.mozilla.org/en-US/docs/Web/API/RTCDtlsTransport
|
|
|
|
*/
|
|
|
|
const getSelectedCandidatePairFromPeer = (peer) => {
|
|
|
|
if (!peer) return null;
|
|
|
|
|
|
|
|
let selectedPair = null;
|
|
|
|
|
|
|
|
const receivers = peer.getReceivers();
|
|
|
|
if (receivers
|
|
|
|
&& receivers[0]
|
|
|
|
&& receivers[0]?.transport?.iceTransport
|
|
|
|
&& typeof receivers[0].transport.iceTransport.getSelectedCandidatePair === 'function') {
|
|
|
|
selectedPair = receivers[0].transport.iceTransport.getSelectedCandidatePair();
|
|
|
|
}
|
|
|
|
|
|
|
|
return selectedPair;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the selected candidates (local and remote) information.
|
|
|
|
* For browsers that support iceTransport property (see
|
|
|
|
* getSelectedCandidatePairFromPeer) we get this info from peer, otherwise
|
|
|
|
* we retrieve this information from getStats() api
|
|
|
|
*
|
|
|
|
* @param {Object} An object {peer?, stats?} containing the peer connection
|
|
|
|
* object and/or the stats
|
|
|
|
* @returns {Object} An Object {local, remote} containing the information about
|
|
|
|
* the selected candidates. For browsers that support the
|
|
|
|
* iceTransport property, the object attribute's type is RTCIceCandidate.
|
|
|
|
* A RTCIceCandidateStats is returned, otherwise.
|
|
|
|
*
|
|
|
|
* For more info see:
|
|
|
|
* https://www.w3.org/TR/webrtc/#dom-rtcicecandidate
|
|
|
|
* and
|
|
|
|
* https://www.w3.org/TR/webrtc-stats/#dom-rtcicecandidatestats
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
const getSelectedCandidates = ({ peer, stats }) => {
|
|
|
|
let selectedPair = getSelectedCandidatePairFromPeer(peer);
|
|
|
|
|
|
|
|
if (selectedPair) return selectedPair;
|
|
|
|
|
|
|
|
if (!stats) return null;
|
|
|
|
|
|
|
|
selectedPair = getSelectedCandidatePairFromStats(stats);
|
|
|
|
|
|
|
|
if (selectedPair) {
|
|
|
|
return {
|
|
|
|
local: stats[selectedPair?.localCandidateId],
|
|
|
|
remote: stats[selectedPair?.remoteCandidateId],
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets the information about private/public ip address from peer
|
|
|
|
* stats. The information retrieved from selected pair from the current
|
|
|
|
* RTCIceTransport and returned in a new Object with format:
|
|
|
|
* {
|
|
|
|
* isUsingTurn: Boolean,
|
|
|
|
* address: String,
|
|
|
|
* relatedAddress: String,
|
|
|
|
* port: Number,
|
|
|
|
* relatedPort: Number,
|
|
|
|
* protocol: String,
|
|
|
|
* candidateType: String,
|
|
|
|
* ufrag: String,
|
|
|
|
* remoteAddress: String,
|
|
|
|
* remotePort: Number,
|
|
|
|
* remoteCandidateType: String,
|
|
|
|
* remoteProtocol: String,
|
|
|
|
* remoteUfrag: String,
|
|
|
|
* dtlsRole: String,
|
|
|
|
* dtlsState: String,
|
|
|
|
* iceRole: String,
|
|
|
|
* iceState: String,
|
|
|
|
* selectedCandidatePairChanges: Number
|
|
|
|
* relayProtocol: String
|
|
|
|
* }
|
|
|
|
*
|
|
|
|
* If users isn't behind NAT, relatedAddress and relatedPort may be null.
|
|
|
|
*
|
|
|
|
* @returns An Object containing the information about the peer's transport.
|
|
|
|
*
|
|
|
|
* For more information see:
|
|
|
|
* https://www.w3.org/TR/webrtc-stats/#dom-rtcicecandidatepairstats
|
|
|
|
* and
|
|
|
|
* https://www.w3.org/TR/webrtc-stats/#dom-rtcicecandidatestats
|
|
|
|
* and
|
|
|
|
* https://www.w3.org/TR/webrtc/#rtcicecandidatetype-enum
|
|
|
|
*/
|
|
|
|
const getTransportStats = async (peer, stats) => {
|
|
|
|
let transports = {};
|
|
|
|
|
|
|
|
if (stats) {
|
|
|
|
const selectedCandidates = getSelectedCandidates({ peer, stats }) || {};
|
|
|
|
const {
|
|
|
|
local: selectedLocalCandidate = {},
|
|
|
|
remote: selectedRemoteCandidate = {},
|
|
|
|
} = selectedCandidates;
|
|
|
|
const candidateType = selectedLocalCandidate?.candidateType || selectedLocalCandidate?.type;
|
|
|
|
const remoteCandidateType = selectedRemoteCandidate?.candidateType
|
|
|
|
|| selectedRemoteCandidate?.type;
|
|
|
|
const isUsingTurn = candidateType ? candidateType === 'relay' : null;
|
|
|
|
// 1 transport per peer connection - we can safely get the first one
|
|
|
|
const transportData = getDataType(stats, 'transport')[0];
|
|
|
|
|
|
|
|
transports = {
|
|
|
|
isUsingTurn,
|
|
|
|
address: selectedLocalCandidate?.address,
|
|
|
|
relatedAddress: selectedLocalCandidate?.relatedAddress,
|
|
|
|
port: selectedLocalCandidate?.port,
|
|
|
|
relatedPort: selectedLocalCandidate?.relatedPort,
|
|
|
|
protocol: selectedLocalCandidate?.protocol,
|
|
|
|
candidateType,
|
|
|
|
ufrag: selectedLocalCandidate?.usernameFragment,
|
|
|
|
remoteAddress: selectedRemoteCandidate?.address,
|
|
|
|
remotePort: selectedRemoteCandidate?.port,
|
|
|
|
remoteCandidateType,
|
|
|
|
remoteProtocol: selectedRemoteCandidate?.protocol,
|
|
|
|
remoteUfrag: selectedRemoteCandidate?.usernameFragment,
|
|
|
|
dtlsRole: transportData?.dtlsRole,
|
|
|
|
dtlsState: transportData?.dtlsState,
|
|
|
|
iceRole: transportData?.iceRole,
|
|
|
|
iceState: transportData?.iceState,
|
|
|
|
selectedCandidatePairChanges: transportData?.selectedCandidatePairChanges,
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
if (isUsingTurn) transports.relayProtocol = selectedLocalCandidate.relayProtocol;
|
|
|
|
}
|
|
|
|
|
|
|
|
return transports;
|
|
|
|
};
|
|
|
|
|
|
|
|
const buildInboundRtpData = (inbound) => {
|
|
|
|
if (!inbound) return {};
|
|
|
|
|
|
|
|
const inboundRtp = {
|
|
|
|
kind: inbound.kind,
|
|
|
|
jitterBufferAverage: inbound.jitterBufferAverage,
|
|
|
|
lastPacketReceivedTimestamp: inbound.lastPacketReceivedTimestamp,
|
|
|
|
packetsLost: inbound.packetsLost,
|
|
|
|
packetsReceived: inbound.packetsReceived,
|
|
|
|
packetsDiscarded: inbound.packetsDiscarded,
|
|
|
|
};
|
|
|
|
|
|
|
|
if (inbound.kind === 'audio') {
|
|
|
|
inboundRtp.totalAudioEnergy = inbound.totalAudioEnergy;
|
|
|
|
} else if (inbound.kind === 'video') {
|
|
|
|
inboundRtp.framesDecoded = inbound.framesDecoded;
|
|
|
|
inboundRtp.framesDropped = inbound.framesDropped;
|
|
|
|
inboundRtp.framesReceived = inbound.framesReceived;
|
|
|
|
inboundRtp.hugeFramesSent = inbound.hugeFramesSent;
|
|
|
|
inboundRtp.keyFramesDecoded = inbound.keyFramesDecoded;
|
|
|
|
inboundRtp.keyFramesReceived = inbound.keyFramesReceived;
|
|
|
|
inboundRtp.totalDecodeTime = inbound.totalDecodeTime;
|
|
|
|
inboundRtp.totalInterFrameDelay = inbound.totalInterFrameDelay;
|
|
|
|
inboundRtp.totalSquaredInterFrameDelay = inbound.totalSquaredInterFrameDelay;
|
|
|
|
}
|
|
|
|
|
|
|
|
return inboundRtp;
|
|
|
|
};
|
|
|
|
|
|
|
|
const buildOutboundRtpData = (outbound) => {
|
|
|
|
if (!outbound) return {};
|
|
|
|
|
|
|
|
const outboundRtp = {
|
|
|
|
kind: outbound.kind,
|
|
|
|
packetsSent: outbound.packetsSent,
|
|
|
|
nackCount: outbound.nackCount,
|
|
|
|
targetBitrate: outbound.targetBitrate,
|
|
|
|
totalPacketSendDelay: outbound.totalPacketSendDelay,
|
|
|
|
};
|
|
|
|
|
|
|
|
if (outbound.kind === 'audio') {
|
|
|
|
outboundRtp.totalAudioEnergy = outbound.totalAudioEnergy;
|
|
|
|
} else if (outbound.kind === 'video') {
|
|
|
|
outboundRtp.framesEncoded = outbound.framesEncoded;
|
|
|
|
outboundRtp.framesSent = outbound.framesSent;
|
|
|
|
outboundRtp.hugeFramesSent = outbound.hugeFramesSent;
|
|
|
|
outboundRtp.keyFramesEncoded = outbound.keyFramesEncoded;
|
|
|
|
outboundRtp.totalEncodeTime = outbound.totalEncodeTime;
|
|
|
|
outboundRtp.totalPacketSendDelay = outbound.totalPacketSendDelay;
|
|
|
|
outboundRtp.firCount = outbound.firCount;
|
|
|
|
outboundRtp.pliCount = outbound.pliCount;
|
|
|
|
outboundRtp.nackCount = outbound.nackCount;
|
|
|
|
outboundRtp.qpsFE = outbound.qpSum / outbound.framesEncoded;
|
|
|
|
}
|
|
|
|
|
|
|
|
return outboundRtp;
|
|
|
|
};
|
|
|
|
|
|
|
|
const getRTCStatsLogMetadata = (stats) => {
|
|
|
|
if (!stats) return {};
|
|
|
|
|
|
|
|
const { transportStats = {} } = stats;
|
|
|
|
|
|
|
|
addExtraInboundNetworkParameters(stats);
|
|
|
|
const selectedPair = getSelectedCandidatePairFromStats(stats);
|
|
|
|
const inbound = getDataType(stats, 'inbound-rtp')[0];
|
|
|
|
const outbound = getDataType(stats, 'outbound-rtp')[0];
|
|
|
|
|
|
|
|
return {
|
|
|
|
inboundRtp: buildInboundRtpData(inbound),
|
|
|
|
outbound: buildOutboundRtpData(outbound),
|
|
|
|
selectedPair: {
|
|
|
|
state: selectedPair?.state,
|
|
|
|
nominated: selectedPair?.nominated,
|
|
|
|
totalRoundTripTime: selectedPair?.totalRoundTripTime,
|
|
|
|
requestsSent: selectedPair?.requestsSent,
|
|
|
|
responsesReceived: selectedPair?.responsesReceived,
|
|
|
|
availableOutgoingBitrate: selectedPair?.availableOutgoingBitrate,
|
|
|
|
availableIncomingBitrate: selectedPair?.availableIncomingBitrate,
|
|
|
|
lastPacketSentTimestamp: selectedPair?.lastPacketSentTimestamp,
|
|
|
|
lastPacketReceivedTimestamp: selectedPair?.lastPacketReceivedTimestamp,
|
|
|
|
},
|
|
|
|
transport: transportStats,
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
2019-11-30 05:48:04 +08:00
|
|
|
export {
|
2024-08-28 01:00:26 +08:00
|
|
|
addExtraInboundNetworkParameters,
|
|
|
|
calculateJitterBufferAverage,
|
|
|
|
getDataType,
|
|
|
|
getTransportStats,
|
|
|
|
getSelectedCandidates,
|
|
|
|
getSelectedCandidatePairFromPeer,
|
|
|
|
getSelectedCandidatePairFromStats,
|
|
|
|
getRTCStatsLogMetadata,
|
2019-11-30 05:48:04 +08:00
|
|
|
monitorAudioConnection,
|
2021-06-13 23:36:43 +08:00
|
|
|
};
|