1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-07-31 15:24:23 +03:00

Refactor naming of webrtc stats reporter (#3404)

* Refactor names in webrtc stats

* Refactor summary stats reporter to gatherer

* Add test for signal change

* rename test
This commit is contained in:
Enrico Schwendig
2023-05-26 09:56:49 +02:00
committed by GitHub
parent b5414ea914
commit 7ade461a4c
17 changed files with 160 additions and 119 deletions

View File

@ -14,21 +14,22 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { StatsReportGatherer } from "../../../../src/webrtc/stats/statsReportGatherer";
import { CallStatsReportGatherer } from "../../../../src/webrtc/stats/callStatsReportGatherer";
import { StatsReportEmitter } from "../../../../src/webrtc/stats/statsReportEmitter";
import { MediaSsrcHandler } from "../../../../src/webrtc/stats/media/mediaSsrcHandler";
const CALL_ID = "CALL_ID";
const USER_ID = "USER_ID";
describe("StatsReportGatherer", () => {
let collector: StatsReportGatherer;
describe("CallStatsReportGatherer", () => {
let collector: CallStatsReportGatherer;
let rtcSpy: RTCPeerConnection;
let emitter: StatsReportEmitter;
beforeEach(() => {
rtcSpy = { getStats: () => new Promise<RTCStatsReport>(() => null) } as RTCPeerConnection;
rtcSpy.addEventListener = jest.fn();
emitter = new StatsReportEmitter();
collector = new StatsReportGatherer(CALL_ID, USER_ID, rtcSpy, emitter);
collector = new CallStatsReportGatherer(CALL_ID, USER_ID, rtcSpy, emitter);
});
describe("on process stats", () => {
@ -145,4 +146,44 @@ describe("StatsReportGatherer", () => {
expect(collector.getActive()).toBeTruthy();
});
});
describe("on signal state change event", () => {
let events: { [key: string]: any };
beforeEach(() => {
events = [];
// Define the addEventListener method with a Jest mock function
rtcSpy.addEventListener = jest.fn((event: any, callback: any) => {
events[event] = callback;
});
collector = new CallStatsReportGatherer(CALL_ID, USER_ID, rtcSpy, emitter);
});
it("in case of stable, parse remote and local description", async () => {
// @ts-ignore
const mediaSsrcHandler = {
parse: jest.fn(),
ssrcToMid: jest.fn(),
findMidBySsrc: jest.fn(),
getSsrcToMidMap: jest.fn(),
} as MediaSsrcHandler;
const remoteSDP = "sdp";
const localSDP = "sdp";
// @ts-ignore
rtcSpy.signalingState = "stable";
// @ts-ignore
rtcSpy.currentRemoteDescription = <RTCSessionDescription>{ sdp: remoteSDP };
// @ts-ignore
rtcSpy.currentLocalDescription = <RTCSessionDescription>{ sdp: localSDP };
// @ts-ignore
collector.trackStats.mediaSsrcHandler = mediaSsrcHandler;
events["signalingstatechange"]();
expect(mediaSsrcHandler.parse).toHaveBeenCalledWith(remoteSDP, "remote");
expect(mediaSsrcHandler.parse).toHaveBeenCalledWith(localSDP, "local");
});
});
});

View File

@ -16,7 +16,7 @@ limitations under the License.
import { TrackID } from "../../../../src/webrtc/stats/statsReport";
import { MediaTrackStats } from "../../../../src/webrtc/stats/media/mediaTrackStats";
import { StatsReportBuilder } from "../../../../src/webrtc/stats/statsReportBuilder";
import { ConnectionStatsReportBuilder } from "../../../../src/webrtc/stats/connectionStatsReportBuilder";
describe("StatsReportBuilder", () => {
const LOCAL_VIDEO_TRACK_ID = "LOCAL_VIDEO_TRACK_ID";
@ -39,7 +39,7 @@ describe("StatsReportBuilder", () => {
describe("should build stats", () => {
it("by media track stats.", async () => {
expect(StatsReportBuilder.build(stats)).toEqual({
expect(ConnectionStatsReportBuilder.build(stats)).toEqual({
bitrate: {
audio: {
download: 4000,

View File

@ -13,7 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { ConnectionStatsReporter } from "../../../../src/webrtc/stats/connectionStatsReporter";
import { ConnectionStatsBuilder } from "../../../../src/webrtc/stats/connectionStatsBuilder";
describe("ConnectionStatsReporter", () => {
describe("should on bandwidth stats", () => {
@ -22,11 +22,11 @@ describe("ConnectionStatsReporter", () => {
availableIncomingBitrate: 1000,
availableOutgoingBitrate: 2000,
} as RTCIceCandidatePairStats;
expect(ConnectionStatsReporter.buildBandwidthReport(stats)).toEqual({ download: 1, upload: 2 });
expect(ConnectionStatsBuilder.buildBandwidthReport(stats)).toEqual({ download: 1, upload: 2 });
});
it("build empty bandwidth report if chromium starts attributes not available", () => {
const stats = {} as RTCIceCandidatePairStats;
expect(ConnectionStatsReporter.buildBandwidthReport(stats)).toEqual({ download: 0, upload: 0 });
expect(ConnectionStatsBuilder.buildBandwidthReport(stats)).toEqual({ download: 0, upload: 0 });
});
});
@ -36,11 +36,11 @@ describe("ConnectionStatsReporter", () => {
availableIncomingBitrate: 1000,
availableOutgoingBitrate: 2000,
} as RTCIceCandidatePairStats;
expect(ConnectionStatsReporter.buildBandwidthReport(stats)).toEqual({ download: 1, upload: 2 });
expect(ConnectionStatsBuilder.buildBandwidthReport(stats)).toEqual({ download: 1, upload: 2 });
});
it("build empty bandwidth report if chromium starts attributes not available", () => {
const stats = {} as RTCIceCandidatePairStats;
expect(ConnectionStatsReporter.buildBandwidthReport(stats)).toEqual({ download: 0, upload: 0 });
expect(ConnectionStatsBuilder.buildBandwidthReport(stats)).toEqual({ download: 0, upload: 0 });
});
});
});

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { GroupCallStats } from "../../../../src/webrtc/stats/groupCallStats";
import { SummaryStats } from "../../../../src/webrtc/stats/summaryStats";
import { CallStatsReportSummary } from "../../../../src/webrtc/stats/callStatsReportSummary";
const GROUP_CALL_ID = "GROUP_ID";
const LOCAL_USER_ID = "LOCAL_USER_ID";
@ -112,7 +112,7 @@ describe("GroupCallStats", () => {
concealedAudio: 0,
totalAudio: 0,
},
} as SummaryStats;
} as CallStatsReportSummary;
let processStatsSpy;
if (collector) {
processStatsSpy = jest.spyOn(collector, "processStats").mockResolvedValue(summaryStats);

View File

@ -13,16 +13,16 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { SummaryStatsReporter } from "../../../../src/webrtc/stats/summaryStatsReporter";
import { SummaryStatsReportGatherer } from "../../../../src/webrtc/stats/summaryStatsReportGatherer";
import { StatsReportEmitter } from "../../../../src/webrtc/stats/statsReportEmitter";
describe("SummaryStatsReporter", () => {
let reporter: SummaryStatsReporter;
describe("SummaryStatsReportGatherer", () => {
let reporter: SummaryStatsReportGatherer;
let emitter: StatsReportEmitter;
beforeEach(() => {
emitter = new StatsReportEmitter();
emitter.emitSummaryStatsReport = jest.fn();
reporter = new SummaryStatsReporter(emitter);
reporter = new SummaryStatsReportGatherer(emitter);
});
describe("build Summary Stats Report", () => {

View File

@ -13,20 +13,20 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { TrackStatsReporter } from "../../../../src/webrtc/stats/trackStatsReporter";
import { TrackStatsBuilder } from "../../../../src/webrtc/stats/trackStatsBuilder";
import { MediaTrackStats } from "../../../../src/webrtc/stats/media/mediaTrackStats";
describe("TrackStatsReporter", () => {
describe("TrackStatsBuilder", () => {
describe("should on frame and resolution stats", () => {
it("creating empty frame and resolution report, if no data available.", async () => {
const trackStats = new MediaTrackStats("1", "local", "video");
TrackStatsReporter.buildFramerateResolution(trackStats, {});
TrackStatsBuilder.buildFramerateResolution(trackStats, {});
expect(trackStats.getFramerate()).toEqual(0);
expect(trackStats.getResolution()).toEqual({ width: -1, height: -1 });
});
it("creating empty frame and resolution report.", async () => {
const trackStats = new MediaTrackStats("1", "remote", "video");
TrackStatsReporter.buildFramerateResolution(trackStats, {
TrackStatsBuilder.buildFramerateResolution(trackStats, {
framesPerSecond: 22.2,
frameHeight: 180,
frameWidth: 360,
@ -39,7 +39,7 @@ describe("TrackStatsReporter", () => {
describe("should on simulcast", () => {
it("creating simulcast framerate.", async () => {
const trackStats = new MediaTrackStats("1", "local", "video");
TrackStatsReporter.calculateSimulcastFramerate(
TrackStatsBuilder.calculateSimulcastFramerate(
trackStats,
{
framesSent: 100,
@ -58,7 +58,7 @@ describe("TrackStatsReporter", () => {
describe("should on bytes received stats", () => {
it("creating build bitrate received report.", async () => {
const trackStats = new MediaTrackStats("1", "remote", "video");
TrackStatsReporter.buildBitrateReceived(
TrackStatsBuilder.buildBitrateReceived(
trackStats,
{
bytesReceived: 2001000,
@ -73,7 +73,7 @@ describe("TrackStatsReporter", () => {
describe("should on bytes send stats", () => {
it("creating build bitrate send report.", async () => {
const trackStats = new MediaTrackStats("1", "local", "video");
TrackStatsReporter.buildBitrateSend(
TrackStatsBuilder.buildBitrateSend(
trackStats,
{
bytesSent: 2001000,
@ -90,7 +90,7 @@ describe("TrackStatsReporter", () => {
const trackStats = new MediaTrackStats("1", "remote", "video");
const remote = {} as RTCStatsReport;
remote.get = jest.fn().mockReturnValue({ mimeType: "video/v8" });
TrackStatsReporter.buildCodec(remote, trackStats, { codecId: "codecID" });
TrackStatsBuilder.buildCodec(remote, trackStats, { codecId: "codecID" });
expect(trackStats.getCodec()).toEqual("v8");
});
});
@ -98,7 +98,7 @@ describe("TrackStatsReporter", () => {
describe("should on package lost stats", () => {
it("creating build package lost on send report.", async () => {
const trackStats = new MediaTrackStats("1", "local", "video");
TrackStatsReporter.buildPacketsLost(
TrackStatsBuilder.buildPacketsLost(
trackStats,
{
type: "outbound-rtp",
@ -114,7 +114,7 @@ describe("TrackStatsReporter", () => {
});
it("creating build package lost on received report.", async () => {
const trackStats = new MediaTrackStats("1", "remote", "video");
TrackStatsReporter.buildPacketsLost(
TrackStatsBuilder.buildPacketsLost(
trackStats,
{
type: "inbound-rtp",
@ -133,7 +133,7 @@ describe("TrackStatsReporter", () => {
describe("should set state of a TrackStats", () => {
it("to not alive if Transceiver undefined", async () => {
const trackStats = new MediaTrackStats("1", "remote", "video");
TrackStatsReporter.setTrackStatsState(trackStats, undefined);
TrackStatsBuilder.setTrackStatsState(trackStats, undefined);
expect(trackStats.alive).toBeFalsy();
});
@ -145,7 +145,7 @@ describe("TrackStatsReporter", () => {
} as RTCRtpSender,
} as RTCRtpTransceiver;
TrackStatsReporter.setTrackStatsState(trackStats, ts);
TrackStatsBuilder.setTrackStatsState(trackStats, ts);
expect(trackStats.alive).toBeFalsy();
});
@ -162,7 +162,7 @@ describe("TrackStatsReporter", () => {
} as RTCRtpReceiver,
} as RTCRtpTransceiver;
TrackStatsReporter.setTrackStatsState(trackStats, ts);
TrackStatsBuilder.setTrackStatsState(trackStats, ts);
expect(trackStats.alive).toBeTruthy();
});
@ -179,7 +179,7 @@ describe("TrackStatsReporter", () => {
} as RTCRtpSender,
} as RTCRtpTransceiver;
TrackStatsReporter.setTrackStatsState(trackStats, ts);
TrackStatsBuilder.setTrackStatsState(trackStats, ts);
expect(trackStats.alive).toBeTruthy();
});
@ -195,7 +195,7 @@ describe("TrackStatsReporter", () => {
} as RTCRtpReceiver,
} as RTCRtpTransceiver;
TrackStatsReporter.setTrackStatsState(trackStats, ts);
TrackStatsBuilder.setTrackStatsState(trackStats, ts);
expect(trackStats.alive).toBeFalsy();
});
@ -211,7 +211,7 @@ describe("TrackStatsReporter", () => {
} as RTCRtpReceiver,
} as RTCRtpTransceiver;
TrackStatsReporter.setTrackStatsState(trackStats, ts);
TrackStatsBuilder.setTrackStatsState(trackStats, ts);
expect(trackStats.alive).toBeTruthy();
expect(trackStats.muted).toBeTruthy();
});
@ -219,7 +219,7 @@ describe("TrackStatsReporter", () => {
describe("should build Track Summary", () => {
it("and returns empty summary if stats list empty", async () => {
const summary = TrackStatsReporter.buildTrackSummary([]);
const summary = TrackStatsBuilder.buildTrackSummary([]);
expect(summary).toEqual({
audioTrackSummary: {
count: 0,
@ -242,7 +242,7 @@ describe("TrackStatsReporter", () => {
it("and returns summary if stats list not empty and ignore local summery", async () => {
const trackStatsList = buildMockTrackStatsList();
const summary = TrackStatsReporter.buildTrackSummary(trackStatsList);
const summary = TrackStatsBuilder.buildTrackSummary(trackStatsList);
expect(summary).toEqual({
audioTrackSummary: {
count: 2,
@ -267,7 +267,7 @@ describe("TrackStatsReporter", () => {
const trackStatsList = buildMockTrackStatsList();
trackStatsList[1].muted = true;
trackStatsList[5].muted = true;
const summary = TrackStatsReporter.buildTrackSummary(trackStatsList);
const summary = TrackStatsBuilder.buildTrackSummary(trackStatsList);
expect(summary).toEqual({
audioTrackSummary: {
count: 2,
@ -292,7 +292,7 @@ describe("TrackStatsReporter", () => {
const trackStatsList = buildMockTrackStatsList();
trackStatsList[1].muted = true;
trackStatsList[1].alive = false;
const summary = TrackStatsReporter.buildTrackSummary(trackStatsList);
const summary = TrackStatsBuilder.buildTrackSummary(trackStatsList);
expect(summary).toEqual({
audioTrackSummary: {
count: 2,
@ -330,7 +330,7 @@ describe("TrackStatsReporter", () => {
trackStatsList[2].setAudioConcealment(220, 2000);
trackStatsList[5].setAudioConcealment(180, 2000);
const summary = TrackStatsReporter.buildTrackSummary(trackStatsList);
const summary = TrackStatsBuilder.buildTrackSummary(trackStatsList);
expect(summary).toEqual({
audioTrackSummary: {
count: 2,
@ -355,25 +355,25 @@ describe("TrackStatsReporter", () => {
describe("should build jitter value in Track Stats", () => {
it("and returns track stats without jitter if report not 'inbound-rtp'", async () => {
const trackStats = new MediaTrackStats("1", "remote", "video");
TrackStatsReporter.buildJitter(trackStats, { jitter: 0.01 });
TrackStatsBuilder.buildJitter(trackStats, { jitter: 0.01 });
expect(trackStats.getJitter()).toEqual(0);
});
it("and returns track stats with jitter", async () => {
const trackStats = new MediaTrackStats("1", "remote", "video");
TrackStatsReporter.buildJitter(trackStats, { type: "inbound-rtp", jitter: 0.01 });
TrackStatsBuilder.buildJitter(trackStats, { type: "inbound-rtp", jitter: 0.01 });
expect(trackStats.getJitter()).toEqual(10);
});
it("and returns negative jitter if stats has no jitter value", async () => {
const trackStats = new MediaTrackStats("1", "remote", "video");
TrackStatsReporter.buildJitter(trackStats, { type: "inbound-rtp" });
TrackStatsBuilder.buildJitter(trackStats, { type: "inbound-rtp" });
expect(trackStats.getJitter()).toEqual(-1);
});
it("and returns jitter as number", async () => {
const trackStats = new MediaTrackStats("1", "remote", "video");
TrackStatsReporter.buildJitter(trackStats, { type: "inbound-rtp", jitter: "0.5" });
TrackStatsBuilder.buildJitter(trackStats, { type: "inbound-rtp", jitter: "0.5" });
expect(trackStats.getJitter()).toEqual(500);
});
});

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { TransportStatsReporter } from "../../../../src/webrtc/stats/transportStatsReporter";
import { TransportStatsBuilder } from "../../../../src/webrtc/stats/transportStatsBuilder";
import { TransportStats } from "../../../../src/webrtc/stats/transportStats";
describe("TransportStatsReporter", () => {
@ -35,7 +35,7 @@ describe("TransportStatsReporter", () => {
it("build new transport stats if all properties there", () => {
const { report, stats } = mockStatsReport(isFocus, 0);
const conferenceStatsTransport: TransportStats[] = [];
const transportStats = TransportStatsReporter.buildReport(report, stats, conferenceStatsTransport, isFocus);
const transportStats = TransportStatsBuilder.buildReport(report, stats, conferenceStatsTransport, isFocus);
expect(transportStats).toEqual([
{
ip: `${remoteIC.ip + 0}:${remoteIC.port}`,
@ -54,8 +54,8 @@ describe("TransportStatsReporter", () => {
const mock1 = mockStatsReport(isFocus, 0);
const mock2 = mockStatsReport(isFocus, 1);
let transportStats: TransportStats[] = [];
transportStats = TransportStatsReporter.buildReport(mock1.report, mock1.stats, transportStats, isFocus);
transportStats = TransportStatsReporter.buildReport(mock2.report, mock2.stats, transportStats, isFocus);
transportStats = TransportStatsBuilder.buildReport(mock1.report, mock1.stats, transportStats, isFocus);
transportStats = TransportStatsBuilder.buildReport(mock2.report, mock2.stats, transportStats, isFocus);
expect(transportStats).toEqual([
{
ip: `${remoteIC.ip + 0}:${remoteIC.port}`,
@ -84,8 +84,8 @@ describe("TransportStatsReporter", () => {
const mock1 = mockStatsReport(isFocus, 0);
const mock2 = mockStatsReport(isFocus, 0);
let transportStats: TransportStats[] = [];
transportStats = TransportStatsReporter.buildReport(mock1.report, mock1.stats, transportStats, isFocus);
transportStats = TransportStatsReporter.buildReport(mock2.report, mock2.stats, transportStats, isFocus);
transportStats = TransportStatsBuilder.buildReport(mock1.report, mock1.stats, transportStats, isFocus);
transportStats = TransportStatsBuilder.buildReport(mock2.report, mock2.stats, transportStats, isFocus);
expect(transportStats).toEqual([
{
ip: `${remoteIC.ip + 0}:${remoteIC.port}`,

View File

@ -13,16 +13,16 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { StatsValueFormatter } from "../../../../src/webrtc/stats/statsValueFormatter";
import { ValueFormatter } from "../../../../src/webrtc/stats/valueFormatter";
describe("StatsValueFormatter", () => {
describe("ValueFormatter", () => {
describe("on get non negative values", () => {
it("formatter shod return number", async () => {
expect(StatsValueFormatter.getNonNegativeValue("2")).toEqual(2);
expect(StatsValueFormatter.getNonNegativeValue(0)).toEqual(0);
expect(StatsValueFormatter.getNonNegativeValue("-2")).toEqual(0);
expect(StatsValueFormatter.getNonNegativeValue("")).toEqual(0);
expect(StatsValueFormatter.getNonNegativeValue(NaN)).toEqual(0);
expect(ValueFormatter.getNonNegativeValue("2")).toEqual(2);
expect(ValueFormatter.getNonNegativeValue(0)).toEqual(0);
expect(ValueFormatter.getNonNegativeValue("-2")).toEqual(0);
expect(ValueFormatter.getNonNegativeValue("")).toEqual(0);
expect(ValueFormatter.getNonNegativeValue(NaN)).toEqual(0);
});
});
});

View File

@ -17,17 +17,17 @@ limitations under the License.
import { ConnectionStats } from "./connectionStats";
import { StatsReportEmitter } from "./statsReportEmitter";
import { ByteSend, ByteSentStatsReport, TrackID } from "./statsReport";
import { ConnectionStatsReporter } from "./connectionStatsReporter";
import { TransportStatsReporter } from "./transportStatsReporter";
import { ConnectionStatsBuilder } from "./connectionStatsBuilder";
import { TransportStatsBuilder } from "./transportStatsBuilder";
import { MediaSsrcHandler } from "./media/mediaSsrcHandler";
import { MediaTrackHandler } from "./media/mediaTrackHandler";
import { MediaTrackStatsHandler } from "./media/mediaTrackStatsHandler";
import { TrackStatsReporter } from "./trackStatsReporter";
import { StatsReportBuilder } from "./statsReportBuilder";
import { StatsValueFormatter } from "./statsValueFormatter";
import { SummaryStats } from "./summaryStats";
import { TrackStatsBuilder } from "./trackStatsBuilder";
import { ConnectionStatsReportBuilder } from "./connectionStatsReportBuilder";
import { ValueFormatter } from "./valueFormatter";
import { CallStatsReportSummary } from "./callStatsReportSummary";
export class StatsReportGatherer {
export class CallStatsReportGatherer {
private isActive = true;
private previousStatsReport: RTCStatsReport | undefined;
private currentStatsReport: RTCStatsReport | undefined;
@ -46,7 +46,7 @@ export class StatsReportGatherer {
this.trackStats = new MediaTrackStatsHandler(new MediaSsrcHandler(), new MediaTrackHandler(pc));
}
public async processStats(groupCallId: string, localUserId: string): Promise<SummaryStats> {
public async processStats(groupCallId: string, localUserId: string): Promise<CallStatsReportSummary> {
const summary = {
isFirstCollection: this.previousStatsReport === undefined,
receivedMedia: 0,
@ -54,7 +54,7 @@ export class StatsReportGatherer {
receivedVideoMedia: 0,
audioTrackSummary: { count: 0, muted: 0, maxPacketLoss: 0, maxJitter: 0, concealedAudio: 0, totalAudio: 0 },
videoTrackSummary: { count: 0, muted: 0, maxPacketLoss: 0, maxJitter: 0, concealedAudio: 0, totalAudio: 0 },
} as SummaryStats;
} as CallStatsReportSummary;
if (this.isActive) {
const statsPromise = this.pc.getStats();
if (typeof statsPromise?.then === "function") {
@ -73,7 +73,7 @@ export class StatsReportGatherer {
summary.receivedMedia = this.connectionStats.bitrate.download;
summary.receivedAudioMedia = this.connectionStats.bitrate.audio?.download || 0;
summary.receivedVideoMedia = this.connectionStats.bitrate.video?.download || 0;
const trackSummary = TrackStatsReporter.buildTrackSummary(
const trackSummary = TrackStatsBuilder.buildTrackSummary(
Array.from(this.trackStats.getTrack2stats().values()),
);
return {
@ -93,14 +93,14 @@ export class StatsReportGatherer {
}
private processStatsReport(groupCallId: string, localUserId: string): void {
const byteSentStats: ByteSentStatsReport = new Map<TrackID, ByteSend>();
const byteSentStatsReport: ByteSentStatsReport = new Map<TrackID, ByteSend>();
this.currentStatsReport?.forEach((now) => {
const before = this.previousStatsReport ? this.previousStatsReport.get(now.id) : null;
// RTCIceCandidatePairStats - https://w3c.github.io/webrtc-stats/#candidatepair-dict*
if (now.type === "candidate-pair" && now.nominated && now.state === "succeeded") {
this.connectionStats.bandwidth = ConnectionStatsReporter.buildBandwidthReport(now);
this.connectionStats.transport = TransportStatsReporter.buildReport(
this.connectionStats.bandwidth = ConnectionStatsBuilder.buildBandwidthReport(now);
this.connectionStats.transport = TransportStatsBuilder.buildReport(
this.currentStatsReport,
now,
this.connectionStats.transport,
@ -121,7 +121,7 @@ export class StatsReportGatherer {
}
if (before) {
TrackStatsReporter.buildPacketsLost(trackStats, now, before);
TrackStatsBuilder.buildPacketsLost(trackStats, now, before);
}
// Get the resolution and framerate for only remote video sources here. For the local video sources,
@ -130,26 +130,26 @@ export class StatsReportGatherer {
// more calculations needed to determine what is the highest resolution stream sent by the client if the
// 'outbound-rtp' stats are used.
if (now.type === "inbound-rtp") {
TrackStatsReporter.buildFramerateResolution(trackStats, now);
TrackStatsBuilder.buildFramerateResolution(trackStats, now);
if (before) {
TrackStatsReporter.buildBitrateReceived(trackStats, now, before);
TrackStatsBuilder.buildBitrateReceived(trackStats, now, before);
}
const ts = this.trackStats.findTransceiverByTrackId(trackStats.trackId);
TrackStatsReporter.setTrackStatsState(trackStats, ts);
TrackStatsReporter.buildJitter(trackStats, now);
TrackStatsReporter.buildAudioConcealment(trackStats, now);
TrackStatsBuilder.setTrackStatsState(trackStats, ts);
TrackStatsBuilder.buildJitter(trackStats, now);
TrackStatsBuilder.buildAudioConcealment(trackStats, now);
} else if (before) {
byteSentStats.set(trackStats.trackId, StatsValueFormatter.getNonNegativeValue(now.bytesSent));
TrackStatsReporter.buildBitrateSend(trackStats, now, before);
byteSentStatsReport.set(trackStats.trackId, ValueFormatter.getNonNegativeValue(now.bytesSent));
TrackStatsBuilder.buildBitrateSend(trackStats, now, before);
}
TrackStatsReporter.buildCodec(this.currentStatsReport, trackStats, now);
TrackStatsBuilder.buildCodec(this.currentStatsReport, trackStats, now);
} else if (now.type === "track" && now.kind === "video" && !now.remoteSource) {
const trackStats = this.trackStats.findLocalVideoTrackStats(now);
if (!trackStats) {
return;
}
TrackStatsReporter.buildFramerateResolution(trackStats, now);
TrackStatsReporter.calculateSimulcastFramerate(
TrackStatsBuilder.buildFramerateResolution(trackStats, now);
TrackStatsBuilder.calculateSimulcastFramerate(
trackStats,
now,
before,
@ -158,8 +158,8 @@ export class StatsReportGatherer {
}
});
this.emitter.emitByteSendReport(byteSentStats);
this.processAndEmitReport();
this.emitter.emitByteSendReport(byteSentStatsReport);
this.processAndEmitConnectionStatsReport();
}
public setActive(isActive: boolean): void {
@ -174,8 +174,8 @@ export class StatsReportGatherer {
this.isActive = false;
}
private processAndEmitReport(): void {
const report = StatsReportBuilder.build(this.trackStats.getTrack2stats());
private processAndEmitConnectionStatsReport(): void {
const report = ConnectionStatsReportBuilder.build(this.trackStats.getTrack2stats());
this.connectionStats.bandwidth = report.bandwidth;
this.connectionStats.bitrate = report.bitrate;

View File

@ -10,7 +10,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
export interface SummaryStats {
export interface CallStatsReportSummary {
receivedMedia: number;
receivedAudioMedia: number;
receivedVideoMedia: number;

View File

@ -15,7 +15,7 @@ limitations under the License.
*/
import { Bitrate } from "./media/mediaTrackStats";
export class ConnectionStatsReporter {
export class ConnectionStatsBuilder {
public static buildBandwidthReport(now: RTCIceCandidatePairStats): Bitrate {
const availableIncomingBitrate = now.availableIncomingBitrate;
const availableOutgoingBitrate = now.availableOutgoingBitrate;

View File

@ -16,7 +16,7 @@ limitations under the License.
import { AudioConcealment, CodecMap, ConnectionStatsReport, FramerateMap, ResolutionMap, TrackID } from "./statsReport";
import { MediaTrackStats, Resolution } from "./media/mediaTrackStats";
export class StatsReportBuilder {
export class ConnectionStatsReportBuilder {
public static build(stats: Map<TrackID, MediaTrackStats>): ConnectionStatsReport {
const report = {} as ConnectionStatsReport;
@ -103,12 +103,12 @@ export class StatsReportBuilder {
};
report.packetLoss = {
total: StatsReportBuilder.calculatePacketLoss(
total: ConnectionStatsReportBuilder.calculatePacketLoss(
lostPackets.download + lostPackets.upload,
totalPackets.download + totalPackets.upload,
),
download: StatsReportBuilder.calculatePacketLoss(lostPackets.download, totalPackets.download),
upload: StatsReportBuilder.calculatePacketLoss(lostPackets.upload, totalPackets.upload),
download: ConnectionStatsReportBuilder.calculatePacketLoss(lostPackets.download, totalPackets.download),
upload: ConnectionStatsReportBuilder.calculatePacketLoss(lostPackets.upload, totalPackets.upload),
};
report.audioConcealment = audioConcealment;
report.totalAudioConcealment = {

View File

@ -13,16 +13,16 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { StatsReportGatherer } from "./statsReportGatherer";
import { CallStatsReportGatherer } from "./callStatsReportGatherer";
import { StatsReportEmitter } from "./statsReportEmitter";
import { SummaryStats } from "./summaryStats";
import { SummaryStatsReporter } from "./summaryStatsReporter";
import { CallStatsReportSummary } from "./callStatsReportSummary";
import { SummaryStatsReportGatherer } from "./summaryStatsReportGatherer";
export class GroupCallStats {
private timer: undefined | ReturnType<typeof setTimeout>;
private readonly gatherers: Map<string, StatsReportGatherer> = new Map<string, StatsReportGatherer>();
private readonly gatherers: Map<string, CallStatsReportGatherer> = new Map<string, CallStatsReportGatherer>();
public readonly reports = new StatsReportEmitter();
private readonly summaryStatsReporter = new SummaryStatsReporter(this.reports);
private readonly summaryStatsReportGatherer = new SummaryStatsReportGatherer(this.reports);
public constructor(private groupCallId: string, private userId: string, private interval: number = 10000) {}
@ -49,7 +49,7 @@ export class GroupCallStats {
if (this.hasStatsReportGatherer(callId)) {
return false;
}
this.gatherers.set(callId, new StatsReportGatherer(callId, userId, peerConnection, this.reports));
this.gatherers.set(callId, new CallStatsReportGatherer(callId, userId, peerConnection, this.reports));
return true;
}
@ -57,17 +57,17 @@ export class GroupCallStats {
return this.gatherers.delete(callId);
}
public getStatsReportGatherer(callId: string): StatsReportGatherer | undefined {
public getStatsReportGatherer(callId: string): CallStatsReportGatherer | undefined {
return this.hasStatsReportGatherer(callId) ? this.gatherers.get(callId) : undefined;
}
private processStats(): void {
const summary: Promise<SummaryStats>[] = [];
const summary: Promise<CallStatsReportSummary>[] = [];
this.gatherers.forEach((c) => {
summary.push(c.processStats(this.groupCallId, this.userId));
});
Promise.all(summary).then((s: Awaited<SummaryStats>[]) => this.summaryStatsReporter.build(s));
Promise.all(summary).then((s: Awaited<CallStatsReportSummary>[]) => this.summaryStatsReportGatherer.build(s));
}
public setInterval(interval: number): void {

View File

@ -11,10 +11,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { StatsReportEmitter } from "./statsReportEmitter";
import { SummaryStats } from "./summaryStats";
import { CallStatsReportSummary } from "./callStatsReportSummary";
import { SummaryStatsReport } from "./statsReport";
interface SummaryCounter {
interface CallStatsReportSummaryCounter {
receivedAudio: number;
receivedVideo: number;
receivedMedia: number;
@ -22,10 +22,10 @@ interface SummaryCounter {
totalAudio: number;
}
export class SummaryStatsReporter {
export class SummaryStatsReportGatherer {
public constructor(private emitter: StatsReportEmitter) {}
public build(allSummary: SummaryStats[]): void {
public build(allSummary: CallStatsReportSummary[]): void {
// Filter all stats which collect the first time webrtc stats.
// Because stats based on time interval and the first collection of a summery stats has no previous
// webrtcStats as basement all the calculation are 0. We don't want track the 0 stats.
@ -34,7 +34,7 @@ export class SummaryStatsReporter {
if (summaryTotalCount === 0) {
return;
}
const summaryCounter: SummaryCounter = {
const summaryCounter: CallStatsReportSummaryCounter = {
receivedAudio: 0,
receivedVideo: 0,
receivedMedia: 0,
@ -70,7 +70,7 @@ export class SummaryStatsReporter {
this.emitter.emitSummaryStatsReport(report);
}
private countTrackListReceivedMedia(counter: SummaryCounter, stats: SummaryStats): void {
private countTrackListReceivedMedia(counter: CallStatsReportSummaryCounter, stats: CallStatsReportSummary): void {
let hasReceivedAudio = false;
let hasReceivedVideo = false;
if (stats.receivedAudioMedia > 0 || stats.audioTrackSummary.count === 0) {
@ -92,7 +92,7 @@ export class SummaryStatsReporter {
}
}
private buildMaxJitter(maxJitter: number, stats: SummaryStats): number {
private buildMaxJitter(maxJitter: number, stats: CallStatsReportSummary): number {
if (maxJitter < stats.videoTrackSummary.maxJitter) {
maxJitter = stats.videoTrackSummary.maxJitter;
}
@ -103,7 +103,7 @@ export class SummaryStatsReporter {
return maxJitter;
}
private buildMaxPacketLoss(maxPacketLoss: number, stats: SummaryStats): number {
private buildMaxPacketLoss(maxPacketLoss: number, stats: CallStatsReportSummary): number {
if (maxPacketLoss < stats.videoTrackSummary.maxPacketLoss) {
maxPacketLoss = stats.videoTrackSummary.maxPacketLoss;
}
@ -114,7 +114,7 @@ export class SummaryStatsReporter {
return maxPacketLoss;
}
private countConcealedAudio(summaryCounter: SummaryCounter, stats: SummaryStats): void {
private countConcealedAudio(summaryCounter: CallStatsReportSummaryCounter, stats: CallStatsReportSummary): void {
summaryCounter.concealedAudio += stats.audioTrackSummary.concealedAudio;
summaryCounter.totalAudio += stats.audioTrackSummary.totalAudio;
}

View File

@ -1,8 +1,8 @@
import { MediaTrackStats } from "./media/mediaTrackStats";
import { StatsValueFormatter } from "./statsValueFormatter";
import { TrackSummary } from "./summaryStats";
import { ValueFormatter } from "./valueFormatter";
import { TrackSummary } from "./callStatsReportSummary";
export class TrackStatsReporter {
export class TrackStatsBuilder {
public static buildFramerateResolution(trackStats: MediaTrackStats, now: any): void {
const resolution = {
height: now.frameHeight,
@ -56,7 +56,7 @@ export class TrackStatsReporter {
public static buildBitrateReceived(trackStats: MediaTrackStats, now: any, before: any): void {
trackStats.setBitrate({
download: TrackStatsReporter.calculateBitrate(
download: TrackStatsBuilder.calculateBitrate(
now.bytesReceived,
before.bytesReceived,
now.timestamp,
@ -81,11 +81,11 @@ export class TrackStatsReporter {
packetsNow = 0;
}
const packetsBefore = StatsValueFormatter.getNonNegativeValue(before[key]);
const packetsBefore = ValueFormatter.getNonNegativeValue(before[key]);
const packetsDiff = Math.max(0, packetsNow - packetsBefore);
const packetsLostNow = StatsValueFormatter.getNonNegativeValue(now.packetsLost);
const packetsLostBefore = StatsValueFormatter.getNonNegativeValue(before.packetsLost);
const packetsLostNow = ValueFormatter.getNonNegativeValue(now.packetsLost);
const packetsLostBefore = ValueFormatter.getNonNegativeValue(before.packetsLost);
const packetsLostDiff = Math.max(0, packetsLostNow - packetsLostBefore);
trackStats.setLoss({
@ -101,8 +101,8 @@ export class TrackStatsReporter {
nowTimestamp: number,
beforeTimestamp: number,
): number {
const bytesNow = StatsValueFormatter.getNonNegativeValue(bytesNowAny);
const bytesBefore = StatsValueFormatter.getNonNegativeValue(bytesBeforeAny);
const bytesNow = ValueFormatter.getNonNegativeValue(bytesNowAny);
const bytesBefore = ValueFormatter.getNonNegativeValue(bytesBeforeAny);
const bytesProcessed = Math.max(0, bytesNow - bytesBefore);
const timeMs = nowTimestamp - beforeTimestamp;
@ -188,7 +188,7 @@ export class TrackStatsReporter {
const jitterStr = statsReport?.jitter;
if (jitterStr !== undefined) {
const jitter = StatsValueFormatter.getNonNegativeValue(jitterStr);
const jitter = ValueFormatter.getNonNegativeValue(jitterStr);
trackStats.setJitter(Math.round(jitter * 1000));
} else {
trackStats.setJitter(-1);

View File

@ -1,6 +1,6 @@
import { TransportStats } from "./transportStats";
export class TransportStatsReporter {
export class TransportStatsBuilder {
public static buildReport(
report: RTCStatsReport | undefined,
now: RTCIceCandidatePairStats,

View File

@ -10,7 +10,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
export class StatsValueFormatter {
export class ValueFormatter {
public static getNonNegativeValue(imput: any): number {
let value = imput;