2019-11-30 05:48:04 +08:00
|
|
|
import logger from '/imports/startup/client/logger';
|
|
|
|
|
2021-04-24 06:15:32 +08:00
|
|
|
// Probes done in an interval
|
|
|
|
const PROBES = 5;
|
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) => {
|
2024-05-29 21:26:11 +08:00
|
|
|
const INTERVAL = window.meetingClientSettings.public.stats.interval / PROBES;
|
2019-11-30 05:48:04 +08:00
|
|
|
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);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
export {
|
|
|
|
monitorAudioConnection,
|
2021-06-13 23:36:43 +08:00
|
|
|
};
|