Skip to content

Commit

Permalink
Implement ICE candidate pairs stats (#35)
Browse files Browse the repository at this point in the history
  • Loading branch information
streamer45 authored Jul 23, 2024
1 parent 31d1be6 commit 5cc5598
Show file tree
Hide file tree
Showing 7 changed files with 155 additions and 19 deletions.
2 changes: 1 addition & 1 deletion lib/rtc_monitor.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ export class RTCMonitor extends EventEmitter {
// - remote-inbound-rtp: metrics for outgoing RTP media streams as received by the remote endpoint.
if (report.type === 'candidate-pair' && report.nominated) {
if (!candidate || (report.priority && candidate.priority && report.priority > candidate.priority)) {
candidate = newRTCCandidatePairStats(report);
candidate = newRTCCandidatePairStats(report, reports);
}
}
if (report.type === 'inbound-rtp' && report.kind === 'audio') {
Expand Down
13 changes: 4 additions & 9 deletions lib/rtc_stats.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { RTCStats } from './types';
import { RTCStats, SSRCStats, ICEStats, RTCCandidatePairStats } from './types';
export declare function newRTCLocalInboundStats(report: any): {
timestamp: any;
mid: any;
Expand All @@ -21,12 +21,7 @@ export declare function newRTCRemoteInboundStats(report: any): {
jitter: any;
roundTripTime: any;
};
export declare function newRTCCandidatePairStats(report: any): {
timestamp: any;
priority: any;
packetsSent: any;
packetsReceived: any;
currentRoundTripTime: any;
totalRoundTripTime: any;
};
export declare function newRTCCandidatePairStats(report: any, reports: RTCStatsReport): RTCCandidatePairStats;
export declare function parseSSRCStats(reports: RTCStatsReport): SSRCStats;
export declare function parseICEStats(reports: RTCStatsReport): ICEStats;
export declare function parseRTCStats(reports: RTCStatsReport): RTCStats;
52 changes: 50 additions & 2 deletions lib/rtc_stats.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,32 @@ export function newRTCRemoteInboundStats(report) {
roundTripTime: report.roundTripTime,
};
}
export function newRTCCandidatePairStats(report) {
export function newRTCCandidatePairStats(report, reports) {
let local;
let remote;
reports.forEach((r) => {
if (r.id === report.localCandidateId) {
local = r;
}
else if (r.id === report.remoteCandidateId) {
remote = r;
}
});
return {
id: report.id,
timestamp: report.timestamp,
priority: report.priority,
packetsSent: report.packetsSent,
packetsReceived: report.packetsReceived,
currentRoundTripTime: report.currentRoundTripTime,
totalRoundTripTime: report.totalRoundTripTime,
nominated: report.nominated,
state: report.state,
local,
remote,
};
}
export function parseRTCStats(reports) {
export function parseSSRCStats(reports) {
const stats = {};
reports.forEach((report) => {
if (!report.ssrc) {
Expand Down Expand Up @@ -81,3 +96,36 @@ export function parseRTCStats(reports) {
});
return stats;
}
export function parseICEStats(reports) {
const stats = {};
reports.forEach((report) => {
if (report.type !== 'candidate-pair') {
return;
}
if (!stats[report.state]) {
stats[report.state] = [];
}
stats[report.state].push(newRTCCandidatePairStats(report, reports));
});
// We sort pairs so that first values are those nominated and/or have the highest priority.
for (const pairs of Object.values(stats)) {
pairs.sort((a, b) => {
var _a, _b;
if (a.nominated && !b.nominated) {
return -1;
}
if (b.nominated && !a.nominated) {
return 1;
}
// Highest priority should come first.
return ((_a = b.priority) !== null && _a !== void 0 ? _a : 0) - ((_b = a.priority) !== null && _b !== void 0 ? _b : 0);
});
}
return stats;
}
export function parseRTCStats(reports) {
return {
ssrcStats: parseSSRCStats(reports),
iceStats: parseICEStats(reports),
};
}
19 changes: 18 additions & 1 deletion lib/types/webrtc.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,19 @@ export type RTCPeerConfig = {
simulcast?: boolean;
connTimeoutMs?: number;
};
export type RTCStats = {
export type SSRCStats = {
[key: number]: {
local: RTCLocalStats;
remote: RTCRemoteStats;
};
};
export type ICEStats = {
[key: string]: RTCCandidatePairStats[];
};
export type RTCStats = {
ssrcStats: SSRCStats;
iceStats: ICEStats;
};
export type RTCLocalStats = {
in?: RTCLocalInboundStats;
out?: RTCLocalOutboundStats;
Expand Down Expand Up @@ -54,13 +61,23 @@ export type RTCRemoteOutboundStats = {
packetsSent: number;
bytesSent: number;
};
export type RTCIceCandidateStats = {
candidateType: string;
protocol: string;
port: number;
};
export type RTCCandidatePairStats = {
id: string;
timestamp: number;
priority?: number;
packetsSent: number;
packetsReceived: number;
currentRoundTripTime: number;
totalRoundTripTime: number;
nominated?: boolean;
state: RTCStatsIceCandidatePairState;
local?: RTCIceCandidateStats;
remote?: RTCIceCandidateStats;
};
export type RTCMonitorConfig = {
peer: RTCPeer;
Expand Down
2 changes: 1 addition & 1 deletion src/rtc_monitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ export class RTCMonitor extends EventEmitter {

if (report.type === 'candidate-pair' && report.nominated) {
if (!candidate || (report.priority && candidate.priority && report.priority > candidate.priority)) {
candidate = newRTCCandidatePairStats(report);
candidate = newRTCCandidatePairStats(report, reports);
}
}

Expand Down
63 changes: 59 additions & 4 deletions src/rtc_stats.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {RTCStats} from './types';
import {RTCStats, SSRCStats, ICEStats, RTCCandidatePairStats} from './types';

export function newRTCLocalInboundStats(report: any) {
return {
Expand Down Expand Up @@ -30,19 +30,34 @@ export function newRTCRemoteInboundStats(report: any) {
};
}

export function newRTCCandidatePairStats(report: any) {
export function newRTCCandidatePairStats(report: any, reports: RTCStatsReport): RTCCandidatePairStats {
let local;
let remote;
reports.forEach((r) => {
if (r.id === report.localCandidateId) {
local = r;
} else if (r.id === report.remoteCandidateId) {
remote = r;
}
});

return {
id: report.id,
timestamp: report.timestamp,
priority: report.priority,
packetsSent: report.packetsSent,
packetsReceived: report.packetsReceived,
currentRoundTripTime: report.currentRoundTripTime,
totalRoundTripTime: report.totalRoundTripTime,
nominated: report.nominated,
state: report.state,
local,
remote,
};
}

export function parseRTCStats(reports: RTCStatsReport): RTCStats {
const stats: RTCStats = {};
export function parseSSRCStats(reports: RTCStatsReport): SSRCStats {
const stats: SSRCStats = {};
reports.forEach((report) => {
if (!report.ssrc) {
return;
Expand Down Expand Up @@ -90,3 +105,43 @@ export function parseRTCStats(reports: RTCStatsReport): RTCStats {
});
return stats;
}

export function parseICEStats(reports: RTCStatsReport): ICEStats {
const stats: ICEStats = {};
reports.forEach((report: RTCIceCandidatePairStats) => {
if (report.type !== 'candidate-pair') {
return;
}

if (!stats[report.state]) {
stats[report.state] = [];
}

stats[report.state].push(newRTCCandidatePairStats(report, reports));
});

// We sort pairs so that first values are those nominated and/or have the highest priority.
for (const pairs of Object.values(stats)) {
pairs.sort((a, b) => {
if (a.nominated && !b.nominated) {
return -1;
}

if (b.nominated && !a.nominated) {
return 1;
}

// Highest priority should come first.
return (b.priority ?? 0) - (a.priority ?? 0);
});
}

return stats;
}

export function parseRTCStats(reports: RTCStatsReport): RTCStats {
return {
ssrcStats: parseSSRCStats(reports),
iceStats: parseICEStats(reports),
};
}
23 changes: 22 additions & 1 deletion src/types/webrtc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,22 @@ export type RTCPeerConfig = {
connTimeoutMs?: number;
}

export type RTCStats = {
export type SSRCStats = {
[key: number]: {
local: RTCLocalStats;
remote: RTCRemoteStats;
}
}

export type ICEStats = {
[key: string]: RTCCandidatePairStats[];
}

export type RTCStats = {
ssrcStats: SSRCStats;
iceStats: ICEStats;
};

export type RTCLocalStats = {
in?: RTCLocalInboundStats;
out?: RTCLocalOutboundStats;
Expand Down Expand Up @@ -64,13 +73,25 @@ export type RTCRemoteOutboundStats = {
bytesSent: number;
}

// This should be in lib.dom.d.ts
export type RTCIceCandidateStats = {
candidateType: string;
protocol: string;
port: number;
}

export type RTCCandidatePairStats = {
id: string;
timestamp: number;
priority?: number;
packetsSent: number;
packetsReceived: number;
currentRoundTripTime: number;
totalRoundTripTime: number;
nominated?: boolean;
state: RTCStatsIceCandidatePairState;
local?: RTCIceCandidateStats;
remote?: RTCIceCandidateStats;
}

export type RTCMonitorConfig = {
Expand Down

0 comments on commit 5cc5598

Please sign in to comment.