openvidu-browser: Refactored webRtcStats

pull/609/head
csantosm 2021-02-24 10:43:23 +01:00
parent a5e022e440
commit e545fd1630
1 changed files with 111 additions and 44 deletions

View File

@ -34,16 +34,45 @@ interface WebrtcStatsConfig {
httpEndpoint: string httpEndpoint: string
} }
interface JSONStats { interface JSONStatsResponse {
'@timestamp': string, '@timestamp': string,
participant_id: string, participant_id: string,
session_id: string, session_id: string,
platform: string, platform: string,
platform_description: string, platform_description: string,
stream: string, stream: string,
webrtc_stats: RTCStatsReport webrtc_stats: IWebrtcStats
} }
interface IWebrtcStats {
inbound: {
audio: {
bytesReceived: number,
packetsReceived: number,
packetsLost: number
} | {},
video: {
bytesReceived: number,
packetsReceived: number,
packetsLost: number,
framesDecoded: number,
nackCount: number
} | {}
} | {},
outbound: {
audio: {
bytesSent: number,
packetsSent: number,
} | {},
video: {
bytesSent: number,
packetsSent: number,
framesEncoded: number,
nackCount: number
} | {}
} | {}
};
export class WebRtcStats { export class WebRtcStats {
private readonly STATS_ITEM_NAME = 'webrtc-stats-config'; private readonly STATS_ITEM_NAME = 'webrtc-stats-config';
@ -66,16 +95,15 @@ export class WebRtcStats {
const webrtcObj = localStorage.getItem(this.STATS_ITEM_NAME); const webrtcObj = localStorage.getItem(this.STATS_ITEM_NAME);
if (!!webrtcObj) { if (!!webrtcObj) {
this.webRtcStatsEnabled = true; this.webRtcStatsEnabled = true;
const webrtcStatsConfig: WebrtcStatsConfig = JSON.parse(webrtcObj); const webrtcStatsConfig: WebrtcStatsConfig = JSON.parse(webrtcObj);
this.POST_URL = webrtcStatsConfig.httpEndpoint;
this.statsInterval = webrtcStatsConfig.interval; // Interval in seconds
// webrtc object found in local storage // webrtc object found in local storage
logger.warn('WebRtc stats enabled for stream ' + this.stream.streamId + ' of connection ' + this.stream.connection.connectionId); logger.warn('WebRtc stats enabled for stream ' + this.stream.streamId + ' of connection ' + this.stream.connection.connectionId);
logger.warn('localStorage item: ' + JSON.stringify(webrtcStatsConfig)); logger.warn('localStorage item: ' + JSON.stringify(webrtcStatsConfig));
this.POST_URL = webrtcStatsConfig.httpEndpoint;
this.statsInterval = webrtcStatsConfig.interval; // Interval in seconds
this.webRtcStatsIntervalId = setInterval(async () => { this.webRtcStatsIntervalId = setInterval(async () => {
await this.sendStatsToHttpEndpoint(); await this.sendStatsToHttpEndpoint();
}, this.statsInterval * 1000); }, this.statsInterval * 1000);
@ -155,13 +183,13 @@ export class WebRtcStats {
} }
} }
private async sendStats(url: string, json: JSONStats): Promise<void> { private async sendStats(url: string, response: JSONStatsResponse): Promise<void> {
try { try {
const configuration: RequestInit = { const configuration: RequestInit = {
headers: { headers: {
'Content-type': 'application/json' 'Content-type': 'application/json'
}, },
body: JSON.stringify(json), body: JSON.stringify(response),
method: 'POST', method: 'POST',
}; };
await fetch(url, configuration); await fetch(url, configuration);
@ -173,34 +201,88 @@ export class WebRtcStats {
private async sendStatsToHttpEndpoint(): Promise<void> { private async sendStatsToHttpEndpoint(): Promise<void> {
try { try {
const stats: RTCStatsReport = await this.getStats(); const webrtcStats: IWebrtcStats = await this.getStats();
const json = this.generateJSONStats(stats); const response = this.generateJSONStatsResponse(webrtcStats);
// this.parseAndSendStats(stats); await this.sendStats(this.POST_URL, response);
await this.sendStats(this.POST_URL, json);
} catch (error) { } catch (error) {
logger.log(error); logger.log(error);
} }
} }
private async getStats(): Promise<any> { private async getStats(): Promise<IWebrtcStats> {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
if (platform.isChromeBrowser() || platform.isChromeMobileBrowser() || platform.isOperaBrowser() || platform.isOperaMobileBrowser()) { if (platform.isChromeBrowser() || platform.isChromeMobileBrowser() || platform.isOperaBrowser() || platform.isOperaMobileBrowser()) {
const pc: any = this.stream.getRTCPeerConnection(); const pc: any = this.stream.getRTCPeerConnection();
pc.getStats((statsReport) => { pc.getStats((statsReport) => {
resolve(this.standardizeReport(statsReport)); const stats = statsReport.result().filter((stat) => stat.type === 'ssrc');
const response = this.initWebRtcStatsResponse();
stats.forEach(stat => {
const valueNames: string[] = stat.names();
const mediaType = stat.stat("mediaType");
const isAudio = mediaType === 'audio' && valueNames.includes('audioOutputLevel');
const isVideo = mediaType === 'video' && valueNames.includes('qpSum');
const isIndoundRtp = valueNames.includes('bytesReceived') && (isAudio || isVideo);
const isOutboundRtp = valueNames.includes('bytesSent');
if(isIndoundRtp){
response.inbound[mediaType].bytesReceived = stat.stat('bytesReceived');
response.inbound[mediaType].packetsReceived = stat.stat('packetsReceived');
response.inbound[mediaType].packetsLost = stat.stat('packetsLost');
if(mediaType === 'video'){
response.inbound['video'].framesDecoded = stat.stat('framesDecoded');
response.inbound['video'].nackCount = stat.stat('nackCount');
}
} else if(isOutboundRtp) {
response.outbound[mediaType].bytesSent = stat.stat('bytesSent');
response.outbound[mediaType].packetsSent = stat.stat('packetsSent');
if(mediaType === 'video'){
response.outbound['video'].framesEncoded = stat.stat('framesEncoded');
response.outbound['video'].nackCount = stat.stat('nackCount');
}
}
});
resolve(response);
}); });
} else { } else {
const statsReport = await this.stream.getRTCPeerConnection().getStats(); const statsReport:any = await this.stream.getRTCPeerConnection().getStats(null);
resolve(this.standardizeReport(statsReport)); const response = this.initWebRtcStatsResponse();
statsReport.forEach((stat: any) => {
const mediaType = stat.mediaType;
// isRemote property has been deprecated from Firefox 66 https://blog.mozilla.org/webrtc/getstats-isremote-66/
switch (stat.type) {
case "outbound-rtp":
response.outbound[mediaType].bytesSent = stat.bytesSent;
response.outbound[mediaType].packetsSent = stat.packetsSent;
if(mediaType === 'video'){
response.outbound[mediaType].framesEncoded = stat.framesEncoded;
}
break;
case "inbound-rtp":
response.inbound[mediaType].bytesReceived = stat.bytesReceived;
response.inbound[mediaType].packetsReceived = stat.packetsReceived;
response.inbound[mediaType].packetsLost = stat.packetsLost;
if (mediaType === 'video') {
response.inbound[mediaType].framesDecoded = stat.framesDecoded;
response.inbound[mediaType].nackCount = stat.nackCount;
}
break;
}
});
return resolve(response);
} }
}); });
} }
private generateJSONStats(stats: RTCStatsReport): JSONStats { private generateJSONStatsResponse(stats: IWebrtcStats): JSONStatsResponse {
return { return {
'@timestamp': new Date().toISOString(), '@timestamp': new Date().toISOString(),
participant_id: this.stream.connection.data, participant_id: this.stream.connection.data,
@ -212,34 +294,19 @@ export class WebRtcStats {
}; };
} }
private standardizeReport(response: RTCStatsReport | any) { private initWebRtcStatsResponse(): IWebrtcStats {
let standardReport = {};
if (platform.isChromeBrowser() || platform.isChromeMobileBrowser() || platform.isOperaBrowser() || platform.isOperaMobileBrowser()) { return {
response.result().forEach(report => { inbound: {
const standardStats = { audio: {},
id: report.id, video: {}
timestamp: report.timestamp, },
type: report.type outbound: {
}; audio: {},
report.names().forEach((name) => { video: {}
standardStats[name] = report.stat(name); }
}); };
standardReport[standardStats.id] = standardStats;
});
return standardReport;
}
// Others platforms
response.forEach((values) => {
let standardStats: any = {};
Object.keys(values).forEach((value: any) => {
standardStats[value] = values[value];
});
standardReport[standardStats.id] = standardStats
});
return standardReport;
} }
} }