summaryrefslogtreecommitdiff
path: root/includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats
diff options
context:
space:
mode:
Diffstat (limited to 'includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats')
-rw-r--r--includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats/connectionStats.ts47
-rw-r--r--includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats/connectionStatsReporter.ts28
-rw-r--r--includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats/groupCallStats.ts64
-rw-r--r--includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats/media/mediaSsrcHandler.ts57
-rw-r--r--includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats/media/mediaTrackHandler.ts71
-rw-r--r--includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats/media/mediaTrackStats.ts104
-rw-r--r--includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats/media/mediaTrackStatsHandler.ts86
-rw-r--r--includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats/statsReport.ts56
-rw-r--r--includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats/statsReportBuilder.ts110
-rw-r--r--includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats/statsReportEmitter.ts33
-rw-r--r--includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats/statsReportGatherer.ts183
-rw-r--r--includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats/statsValueFormatter.ts27
-rw-r--r--includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats/trackStatsReporter.ts117
-rw-r--r--includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats/transportStats.ts26
-rw-r--r--includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats/transportStatsReporter.ts48
15 files changed, 1057 insertions, 0 deletions
diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats/connectionStats.ts b/includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats/connectionStats.ts
new file mode 100644
index 0000000..dbde6e5
--- /dev/null
+++ b/includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats/connectionStats.ts
@@ -0,0 +1,47 @@
+/*
+Copyright 2023 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+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 { TransportStats } from "./transportStats";
+import { Bitrate } from "./media/mediaTrackStats";
+
+export interface ConnectionStatsBandwidth {
+ /**
+ * bytes per second
+ */
+ download: number;
+ /**
+ * bytes per second
+ */
+ upload: number;
+}
+
+export interface ConnectionStatsBitrate extends Bitrate {
+ audio?: Bitrate;
+ video?: Bitrate;
+}
+
+export interface PacketLoos {
+ total: number;
+ download: number;
+ upload: number;
+}
+
+export class ConnectionStats {
+ public bandwidth: ConnectionStatsBitrate = {} as ConnectionStatsBitrate;
+ public bitrate: ConnectionStatsBitrate = {} as ConnectionStatsBitrate;
+ public packetLoss: PacketLoos = {} as PacketLoos;
+ public transport: TransportStats[] = [];
+}
diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats/connectionStatsReporter.ts b/includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats/connectionStatsReporter.ts
new file mode 100644
index 0000000..c43b9b4
--- /dev/null
+++ b/includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats/connectionStatsReporter.ts
@@ -0,0 +1,28 @@
+/*
+Copyright 2023 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+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 { Bitrate } from "./media/mediaTrackStats";
+
+export class ConnectionStatsReporter {
+ public static buildBandwidthReport(now: RTCIceCandidatePairStats): Bitrate {
+ const availableIncomingBitrate = now.availableIncomingBitrate;
+ const availableOutgoingBitrate = now.availableOutgoingBitrate;
+
+ return {
+ download: availableIncomingBitrate ? Math.round(availableIncomingBitrate / 1000) : 0,
+ upload: availableOutgoingBitrate ? Math.round(availableOutgoingBitrate / 1000) : 0,
+ };
+ }
+}
diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats/groupCallStats.ts b/includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats/groupCallStats.ts
new file mode 100644
index 0000000..6d8c566
--- /dev/null
+++ b/includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats/groupCallStats.ts
@@ -0,0 +1,64 @@
+/*
+Copyright 2023 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+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 { StatsReportEmitter } from "./statsReportEmitter";
+
+export class GroupCallStats {
+ private timer: undefined | ReturnType<typeof setTimeout>;
+ private readonly gatherers: Map<string, StatsReportGatherer> = new Map<string, StatsReportGatherer>();
+ public readonly reports = new StatsReportEmitter();
+
+ public constructor(private groupCallId: string, private userId: string, private interval: number = 10000) {}
+
+ public start(): void {
+ if (this.timer === undefined) {
+ this.timer = setInterval(() => {
+ this.processStats();
+ }, this.interval);
+ }
+ }
+
+ public stop(): void {
+ if (this.timer !== undefined) {
+ clearInterval(this.timer);
+ this.gatherers.forEach((c) => c.stopProcessingStats());
+ }
+ }
+
+ public hasStatsReportGatherer(callId: string): boolean {
+ return this.gatherers.has(callId);
+ }
+
+ public addStatsReportGatherer(callId: string, userId: string, peerConnection: RTCPeerConnection): boolean {
+ if (this.hasStatsReportGatherer(callId)) {
+ return false;
+ }
+ this.gatherers.set(callId, new StatsReportGatherer(callId, userId, peerConnection, this.reports));
+ return true;
+ }
+
+ public removeStatsReportGatherer(callId: string): boolean {
+ return this.gatherers.delete(callId);
+ }
+
+ public getStatsReportGatherer(callId: string): StatsReportGatherer | undefined {
+ return this.hasStatsReportGatherer(callId) ? this.gatherers.get(callId) : undefined;
+ }
+
+ private processStats(): void {
+ this.gatherers.forEach((c) => c.processStats(this.groupCallId, this.userId));
+ }
+}
diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats/media/mediaSsrcHandler.ts b/includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats/media/mediaSsrcHandler.ts
new file mode 100644
index 0000000..e606051
--- /dev/null
+++ b/includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats/media/mediaSsrcHandler.ts
@@ -0,0 +1,57 @@
+/*
+Copyright 2023 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+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 { parse as parseSdp } from "sdp-transform";
+
+export type Mid = string;
+export type Ssrc = string;
+export type MapType = "local" | "remote";
+
+export class MediaSsrcHandler {
+ private readonly ssrcToMid = { local: new Map<Mid, Ssrc[]>(), remote: new Map<Mid, Ssrc[]>() };
+
+ public findMidBySsrc(ssrc: Ssrc, type: "local" | "remote"): Mid | undefined {
+ let mid: Mid | undefined;
+ this.ssrcToMid[type].forEach((ssrcs, m) => {
+ if (ssrcs.find((s) => s == ssrc)) {
+ mid = m;
+ return;
+ }
+ });
+ return mid;
+ }
+
+ public parse(description: string, type: MapType): void {
+ const sdp = parseSdp(description);
+ const ssrcToMid = new Map<Mid, Ssrc[]>();
+ sdp.media.forEach((m) => {
+ if ((!!m.mid && m.type === "video") || m.type === "audio") {
+ const ssrcs: Ssrc[] = [];
+ m.ssrcs?.forEach((ssrc) => {
+ if (ssrc.attribute === "cname") {
+ ssrcs.push(`${ssrc.id}`);
+ }
+ });
+ ssrcToMid.set(`${m.mid}`, ssrcs);
+ }
+ });
+ this.ssrcToMid[type] = ssrcToMid;
+ }
+
+ public getSsrcToMidMap(type: MapType): Map<Mid, Ssrc[]> {
+ return this.ssrcToMid[type];
+ }
+}
diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats/media/mediaTrackHandler.ts b/includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats/media/mediaTrackHandler.ts
new file mode 100644
index 0000000..32580b1
--- /dev/null
+++ b/includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats/media/mediaTrackHandler.ts
@@ -0,0 +1,71 @@
+/*
+Copyright 2023 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+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 type TrackId = string;
+
+export class MediaTrackHandler {
+ public constructor(private readonly pc: RTCPeerConnection) {}
+
+ public getLocalTracks(kind: "audio" | "video"): MediaStreamTrack[] {
+ const isNotNullAndKind = (track: MediaStreamTrack | null): boolean => {
+ return track !== null && track.kind === kind;
+ };
+ // @ts-ignore The linter don't get it
+ return this.pc
+ .getTransceivers()
+ .filter((t) => t.currentDirection === "sendonly" || t.currentDirection === "sendrecv")
+ .filter((t) => t.sender !== null)
+ .map((t) => t.sender)
+ .map((s) => s.track)
+ .filter(isNotNullAndKind);
+ }
+
+ public getTackById(trackId: string): MediaStreamTrack | undefined {
+ return this.pc
+ .getTransceivers()
+ .map((t) => {
+ if (t?.sender.track !== null && t.sender.track.id === trackId) {
+ return t.sender.track;
+ }
+ if (t?.receiver.track !== null && t.receiver.track.id === trackId) {
+ return t.receiver.track;
+ }
+ return undefined;
+ })
+ .find((t) => t !== undefined);
+ }
+
+ public getLocalTrackIdByMid(mid: string): string | undefined {
+ const transceiver = this.pc.getTransceivers().find((t) => t.mid === mid);
+ if (transceiver !== undefined && !!transceiver.sender && !!transceiver.sender.track) {
+ return transceiver.sender.track.id;
+ }
+ return undefined;
+ }
+
+ public getRemoteTrackIdByMid(mid: string): string | undefined {
+ const transceiver = this.pc.getTransceivers().find((t) => t.mid === mid);
+ if (transceiver !== undefined && !!transceiver.receiver && !!transceiver.receiver.track) {
+ return transceiver.receiver.track.id;
+ }
+ return undefined;
+ }
+
+ public getActiveSimulcastStreams(): number {
+ //@TODO implement this right.. Check how many layer configured
+ return 3;
+ }
+}
diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats/media/mediaTrackStats.ts b/includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats/media/mediaTrackStats.ts
new file mode 100644
index 0000000..69ee9bd
--- /dev/null
+++ b/includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats/media/mediaTrackStats.ts
@@ -0,0 +1,104 @@
+/*
+Copyright 2023 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+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 { TrackId } from "./mediaTrackHandler";
+
+export interface PacketLoss {
+ packetsTotal: number;
+ packetsLost: number;
+ isDownloadStream: boolean;
+}
+
+export interface Bitrate {
+ /**
+ * bytes per second
+ */
+ download: number;
+ /**
+ * bytes per second
+ */
+ upload: number;
+}
+
+export interface Resolution {
+ width: number;
+ height: number;
+}
+
+export type TrackStatsType = "local" | "remote";
+
+export class MediaTrackStats {
+ private loss: PacketLoss = { packetsTotal: 0, packetsLost: 0, isDownloadStream: false };
+ private bitrate: Bitrate = { download: 0, upload: 0 };
+ private resolution: Resolution = { width: -1, height: -1 };
+ private framerate = 0;
+ private codec = "";
+
+ public constructor(
+ public readonly trackId: TrackId,
+ public readonly type: TrackStatsType,
+ public readonly kind: "audio" | "video",
+ ) {}
+
+ public getType(): TrackStatsType {
+ return this.type;
+ }
+
+ public setLoss(loos: PacketLoss): void {
+ this.loss = loos;
+ }
+
+ public getLoss(): PacketLoss {
+ return this.loss;
+ }
+
+ public setResolution(resolution: Resolution): void {
+ this.resolution = resolution;
+ }
+
+ public getResolution(): Resolution {
+ return this.resolution;
+ }
+
+ public setFramerate(framerate: number): void {
+ this.framerate = framerate;
+ }
+
+ public getFramerate(): number {
+ return this.framerate;
+ }
+
+ public setBitrate(bitrate: Bitrate): void {
+ this.bitrate = bitrate;
+ }
+
+ public getBitrate(): Bitrate {
+ return this.bitrate;
+ }
+
+ public setCodec(codecShortType: string): boolean {
+ this.codec = codecShortType;
+ return true;
+ }
+
+ public getCodec(): string {
+ return this.codec;
+ }
+
+ public resetBitrate(): void {
+ this.bitrate = { download: 0, upload: 0 };
+ }
+}
diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats/media/mediaTrackStatsHandler.ts b/includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats/media/mediaTrackStatsHandler.ts
new file mode 100644
index 0000000..6fb119c
--- /dev/null
+++ b/includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats/media/mediaTrackStatsHandler.ts
@@ -0,0 +1,86 @@
+/*
+Copyright 2023 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+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 { TrackID } from "../statsReport";
+import { MediaTrackStats } from "./mediaTrackStats";
+import { MediaTrackHandler } from "./mediaTrackHandler";
+import { MediaSsrcHandler } from "./mediaSsrcHandler";
+
+export class MediaTrackStatsHandler {
+ private readonly track2stats = new Map<TrackID, MediaTrackStats>();
+
+ public constructor(
+ public readonly mediaSsrcHandler: MediaSsrcHandler,
+ public readonly mediaTrackHandler: MediaTrackHandler,
+ ) {}
+
+ /**
+ * Find tracks by rtc stats
+ * Argument report is any because the stats api is not consistent:
+ * For example `trackIdentifier`, `mid` not existing in every implementations
+ * https://www.w3.org/TR/webrtc-stats/#dom-rtcinboundrtpstreamstats
+ * https://developer.mozilla.org/en-US/docs/Web/API/RTCInboundRtpStreamStats
+ */
+ public findTrack2Stats(report: any, type: "remote" | "local"): MediaTrackStats | undefined {
+ let trackID;
+ if (report.trackIdentifier) {
+ trackID = report.trackIdentifier;
+ } else if (report.mid) {
+ trackID =
+ type === "remote"
+ ? this.mediaTrackHandler.getRemoteTrackIdByMid(report.mid)
+ : this.mediaTrackHandler.getLocalTrackIdByMid(report.mid);
+ } else if (report.ssrc) {
+ const mid = this.mediaSsrcHandler.findMidBySsrc(report.ssrc, type);
+ if (!mid) {
+ return undefined;
+ }
+ trackID =
+ type === "remote"
+ ? this.mediaTrackHandler.getRemoteTrackIdByMid(report.mid)
+ : this.mediaTrackHandler.getLocalTrackIdByMid(report.mid);
+ }
+
+ if (!trackID) {
+ return undefined;
+ }
+
+ let trackStats = this.track2stats.get(trackID);
+
+ if (!trackStats) {
+ const track = this.mediaTrackHandler.getTackById(trackID);
+ if (track !== undefined) {
+ const kind: "audio" | "video" = track.kind === "audio" ? track.kind : "video";
+ trackStats = new MediaTrackStats(trackID, type, kind);
+ this.track2stats.set(trackID, trackStats);
+ } else {
+ return undefined;
+ }
+ }
+ return trackStats;
+ }
+
+ public findLocalVideoTrackStats(report: any): MediaTrackStats | undefined {
+ const localVideoTracks = this.mediaTrackHandler.getLocalTracks("video");
+ if (localVideoTracks.length === 0) {
+ return undefined;
+ }
+ return this.findTrack2Stats(report, "local");
+ }
+
+ public getTrack2stats(): Map<TrackID, MediaTrackStats> {
+ return this.track2stats;
+ }
+}
diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats/statsReport.ts b/includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats/statsReport.ts
new file mode 100644
index 0000000..56d6c4b
--- /dev/null
+++ b/includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats/statsReport.ts
@@ -0,0 +1,56 @@
+/*
+Copyright 2023 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+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 { ConnectionStatsBandwidth, ConnectionStatsBitrate, PacketLoos } from "./connectionStats";
+import { TransportStats } from "./transportStats";
+import { Resolution } from "./media/mediaTrackStats";
+
+export enum StatsReport {
+ CONNECTION_STATS = "StatsReport.connection_stats",
+ BYTE_SENT_STATS = "StatsReport.byte_sent_stats",
+}
+
+export type TrackID = string;
+export type ByteSend = number;
+
+export interface ByteSentStatsReport extends Map<TrackID, ByteSend> {
+ // is a map: `local trackID` => byte send
+}
+
+export interface ConnectionStatsReport {
+ bandwidth: ConnectionStatsBandwidth;
+ bitrate: ConnectionStatsBitrate;
+ packetLoss: PacketLoos;
+ resolution: ResolutionMap;
+ framerate: FramerateMap;
+ codec: CodecMap;
+ transport: TransportStats[];
+}
+
+export interface ResolutionMap {
+ local: Map<TrackID, Resolution>;
+ remote: Map<TrackID, Resolution>;
+}
+
+export interface FramerateMap {
+ local: Map<TrackID, number>;
+ remote: Map<TrackID, number>;
+}
+
+export interface CodecMap {
+ local: Map<TrackID, string>;
+ remote: Map<TrackID, string>;
+}
diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats/statsReportBuilder.ts b/includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats/statsReportBuilder.ts
new file mode 100644
index 0000000..c1af471
--- /dev/null
+++ b/includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats/statsReportBuilder.ts
@@ -0,0 +1,110 @@
+/*
+Copyright 2023 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+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 { CodecMap, ConnectionStatsReport, FramerateMap, ResolutionMap, TrackID } from "./statsReport";
+import { MediaTrackStats, Resolution } from "./media/mediaTrackStats";
+
+export class StatsReportBuilder {
+ public static build(stats: Map<TrackID, MediaTrackStats>): ConnectionStatsReport {
+ const report = {} as ConnectionStatsReport;
+
+ // process stats
+ const totalPackets = {
+ download: 0,
+ upload: 0,
+ };
+ const lostPackets = {
+ download: 0,
+ upload: 0,
+ };
+ let bitrateDownload = 0;
+ let bitrateUpload = 0;
+ const resolutions: ResolutionMap = {
+ local: new Map<TrackID, Resolution>(),
+ remote: new Map<TrackID, Resolution>(),
+ };
+ const framerates: FramerateMap = { local: new Map<TrackID, number>(), remote: new Map<TrackID, number>() };
+ const codecs: CodecMap = { local: new Map<TrackID, string>(), remote: new Map<TrackID, string>() };
+
+ let audioBitrateDownload = 0;
+ let audioBitrateUpload = 0;
+ let videoBitrateDownload = 0;
+ let videoBitrateUpload = 0;
+
+ for (const [trackId, trackStats] of stats) {
+ // process packet loss stats
+ const loss = trackStats.getLoss();
+ const type = loss.isDownloadStream ? "download" : "upload";
+
+ totalPackets[type] += loss.packetsTotal;
+ lostPackets[type] += loss.packetsLost;
+
+ // process bitrate stats
+ bitrateDownload += trackStats.getBitrate().download;
+ bitrateUpload += trackStats.getBitrate().upload;
+
+ // collect resolutions and framerates
+ if (trackStats.kind === "audio") {
+ audioBitrateDownload += trackStats.getBitrate().download;
+ audioBitrateUpload += trackStats.getBitrate().upload;
+ } else {
+ videoBitrateDownload += trackStats.getBitrate().download;
+ videoBitrateUpload += trackStats.getBitrate().upload;
+ }
+
+ resolutions[trackStats.getType()].set(trackId, trackStats.getResolution());
+ framerates[trackStats.getType()].set(trackId, trackStats.getFramerate());
+ codecs[trackStats.getType()].set(trackId, trackStats.getCodec());
+
+ trackStats.resetBitrate();
+ }
+
+ report.bitrate = {
+ upload: bitrateUpload,
+ download: bitrateDownload,
+ };
+
+ report.bitrate.audio = {
+ upload: audioBitrateUpload,
+ download: audioBitrateDownload,
+ };
+
+ report.bitrate.video = {
+ upload: videoBitrateUpload,
+ download: videoBitrateDownload,
+ };
+
+ report.packetLoss = {
+ total: StatsReportBuilder.calculatePacketLoss(
+ lostPackets.download + lostPackets.upload,
+ totalPackets.download + totalPackets.upload,
+ ),
+ download: StatsReportBuilder.calculatePacketLoss(lostPackets.download, totalPackets.download),
+ upload: StatsReportBuilder.calculatePacketLoss(lostPackets.upload, totalPackets.upload),
+ };
+ report.framerate = framerates;
+ report.resolution = resolutions;
+ report.codec = codecs;
+ return report;
+ }
+
+ private static calculatePacketLoss(lostPackets: number, totalPackets: number): number {
+ if (!totalPackets || totalPackets <= 0 || !lostPackets || lostPackets <= 0) {
+ return 0;
+ }
+
+ return Math.round((lostPackets / totalPackets) * 100);
+ }
+}
diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats/statsReportEmitter.ts b/includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats/statsReportEmitter.ts
new file mode 100644
index 0000000..cf01470
--- /dev/null
+++ b/includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats/statsReportEmitter.ts
@@ -0,0 +1,33 @@
+/*
+Copyright 2023 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+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 { TypedEventEmitter } from "../../models/typed-event-emitter";
+import { ByteSentStatsReport, ConnectionStatsReport, StatsReport } from "./statsReport";
+
+export type StatsReportHandlerMap = {
+ [StatsReport.BYTE_SENT_STATS]: (report: ByteSentStatsReport) => void;
+ [StatsReport.CONNECTION_STATS]: (report: ConnectionStatsReport) => void;
+};
+
+export class StatsReportEmitter extends TypedEventEmitter<StatsReport, StatsReportHandlerMap> {
+ public emitByteSendReport(byteSentStats: ByteSentStatsReport): void {
+ this.emit(StatsReport.BYTE_SENT_STATS, byteSentStats);
+ }
+
+ public emitConnectionStatsReport(report: ConnectionStatsReport): void {
+ this.emit(StatsReport.CONNECTION_STATS, report);
+ }
+}
diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats/statsReportGatherer.ts b/includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats/statsReportGatherer.ts
new file mode 100644
index 0000000..769ba6e
--- /dev/null
+++ b/includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats/statsReportGatherer.ts
@@ -0,0 +1,183 @@
+/*
+Copyright 2023 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+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 { ConnectionStats } from "./connectionStats";
+import { StatsReportEmitter } from "./statsReportEmitter";
+import { ByteSend, ByteSentStatsReport, TrackID } from "./statsReport";
+import { ConnectionStatsReporter } from "./connectionStatsReporter";
+import { TransportStatsReporter } from "./transportStatsReporter";
+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";
+
+export class StatsReportGatherer {
+ private isActive = true;
+ private previousStatsReport: RTCStatsReport | undefined;
+ private currentStatsReport: RTCStatsReport | undefined;
+ private readonly connectionStats = new ConnectionStats();
+
+ private readonly trackStats: MediaTrackStatsHandler;
+
+ // private readonly ssrcToMid = { local: new Map<Mid, Ssrc[]>(), remote: new Map<Mid, Ssrc[]>() };
+
+ public constructor(
+ public readonly callId: string,
+ public readonly remoteUserId: string,
+ private readonly pc: RTCPeerConnection,
+ private readonly emitter: StatsReportEmitter,
+ private readonly isFocus = true,
+ ) {
+ pc.addEventListener("signalingstatechange", this.onSignalStateChange.bind(this));
+ this.trackStats = new MediaTrackStatsHandler(new MediaSsrcHandler(), new MediaTrackHandler(pc));
+ }
+
+ public async processStats(groupCallId: string, localUserId: string): Promise<boolean> {
+ if (this.isActive) {
+ const statsPromise = this.pc.getStats();
+ if (typeof statsPromise?.then === "function") {
+ return statsPromise
+ .then((report) => {
+ // @ts-ignore
+ this.currentStatsReport = typeof report?.result === "function" ? report.result() : report;
+ try {
+ this.processStatsReport(groupCallId, localUserId);
+ } catch (error) {
+ this.isActive = false;
+ return false;
+ }
+
+ this.previousStatsReport = this.currentStatsReport;
+ return true;
+ })
+ .catch((error) => {
+ this.handleError(error);
+ return false;
+ });
+ }
+ this.isActive = false;
+ }
+ return Promise.resolve(false);
+ }
+
+ private processStatsReport(groupCallId: string, localUserId: string): void {
+ const byteSentStats: 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.currentStatsReport,
+ now,
+ this.connectionStats.transport,
+ this.isFocus,
+ );
+
+ // RTCReceivedRtpStreamStats
+ // https://w3c.github.io/webrtc-stats/#receivedrtpstats-dict*
+ // RTCSentRtpStreamStats
+ // https://w3c.github.io/webrtc-stats/#sentrtpstats-dict*
+ } else if (now.type === "inbound-rtp" || now.type === "outbound-rtp") {
+ const trackStats = this.trackStats.findTrack2Stats(
+ now,
+ now.type === "inbound-rtp" ? "remote" : "local",
+ );
+ if (!trackStats) {
+ return;
+ }
+
+ if (before) {
+ TrackStatsReporter.buildPacketsLost(trackStats, now, before);
+ }
+
+ // Get the resolution and framerate for only remote video sources here. For the local video sources,
+ // 'track' stats will be used since they have the updated resolution based on the simulcast streams
+ // currently being sent. Promise based getStats reports three 'outbound-rtp' streams and there will be
+ // 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);
+ if (before) {
+ TrackStatsReporter.buildBitrateReceived(trackStats, now, before);
+ }
+ } else if (before) {
+ byteSentStats.set(trackStats.trackId, StatsValueFormatter.getNonNegativeValue(now.bytesSent));
+ TrackStatsReporter.buildBitrateSend(trackStats, now, before);
+ }
+ TrackStatsReporter.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(
+ trackStats,
+ now,
+ before,
+ this.trackStats.mediaTrackHandler.getActiveSimulcastStreams(),
+ );
+ }
+ });
+
+ this.emitter.emitByteSendReport(byteSentStats);
+ this.processAndEmitReport();
+ }
+
+ public setActive(isActive: boolean): void {
+ this.isActive = isActive;
+ }
+
+ public getActive(): boolean {
+ return this.isActive;
+ }
+
+ private handleError(_: any): void {
+ this.isActive = false;
+ }
+
+ private processAndEmitReport(): void {
+ const report = StatsReportBuilder.build(this.trackStats.getTrack2stats());
+
+ this.connectionStats.bandwidth = report.bandwidth;
+ this.connectionStats.bitrate = report.bitrate;
+ this.connectionStats.packetLoss = report.packetLoss;
+
+ this.emitter.emitConnectionStatsReport({
+ ...report,
+ transport: this.connectionStats.transport,
+ });
+
+ this.connectionStats.transport = [];
+ }
+
+ public stopProcessingStats(): void {}
+
+ private onSignalStateChange(): void {
+ if (this.pc.signalingState === "stable") {
+ if (this.pc.currentRemoteDescription) {
+ this.trackStats.mediaSsrcHandler.parse(this.pc.currentRemoteDescription.sdp, "remote");
+ }
+ if (this.pc.currentLocalDescription) {
+ this.trackStats.mediaSsrcHandler.parse(this.pc.currentLocalDescription.sdp, "local");
+ }
+ }
+ }
+}
diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats/statsValueFormatter.ts b/includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats/statsValueFormatter.ts
new file mode 100644
index 0000000..c658fa6
--- /dev/null
+++ b/includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats/statsValueFormatter.ts
@@ -0,0 +1,27 @@
+/*
+Copyright 2023 The Matrix.org Foundation C.I.C.
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+ http://www.apache.org/licenses/LICENSE-2.0
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+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 {
+ public static getNonNegativeValue(imput: any): number {
+ let value = imput;
+
+ if (typeof value !== "number") {
+ value = Number(value);
+ }
+
+ if (isNaN(value)) {
+ return 0;
+ }
+
+ return Math.max(0, value);
+ }
+}
diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats/trackStatsReporter.ts b/includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats/trackStatsReporter.ts
new file mode 100644
index 0000000..1f6fcd6
--- /dev/null
+++ b/includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats/trackStatsReporter.ts
@@ -0,0 +1,117 @@
+import { MediaTrackStats } from "./media/mediaTrackStats";
+import { StatsValueFormatter } from "./statsValueFormatter";
+
+export class TrackStatsReporter {
+ public static buildFramerateResolution(trackStats: MediaTrackStats, now: any): void {
+ const resolution = {
+ height: now.frameHeight,
+ width: now.frameWidth,
+ };
+ const frameRate = now.framesPerSecond;
+
+ if (resolution.height && resolution.width) {
+ trackStats.setResolution(resolution);
+ }
+ trackStats.setFramerate(Math.round(frameRate || 0));
+ }
+
+ public static calculateSimulcastFramerate(trackStats: MediaTrackStats, now: any, before: any, layer: number): void {
+ let frameRate = trackStats.getFramerate();
+ if (!frameRate) {
+ if (before) {
+ const timeMs = now.timestamp - before.timestamp;
+
+ if (timeMs > 0 && now.framesSent) {
+ const numberOfFramesSinceBefore = now.framesSent - before.framesSent;
+
+ frameRate = (numberOfFramesSinceBefore / timeMs) * 1000;
+ }
+ }
+
+ if (!frameRate) {
+ return;
+ }
+ }
+
+ // Reset frame rate to 0 when video is suspended as a result of endpoint falling out of last-n.
+ frameRate = layer ? Math.round(frameRate / layer) : 0;
+ trackStats.setFramerate(frameRate);
+ }
+
+ public static buildCodec(report: RTCStatsReport | undefined, trackStats: MediaTrackStats, now: any): void {
+ const codec = report?.get(now.codecId);
+
+ if (codec) {
+ /**
+ * The mime type has the following form: video/VP8 or audio/ISAC,
+ * so we what to keep just the type after the '/', audio and video
+ * keys will be added on the processing side.
+ */
+ const codecShortType = codec.mimeType.split("/")[1];
+
+ codecShortType && trackStats.setCodec(codecShortType);
+ }
+ }
+
+ public static buildBitrateReceived(trackStats: MediaTrackStats, now: any, before: any): void {
+ trackStats.setBitrate({
+ download: TrackStatsReporter.calculateBitrate(
+ now.bytesReceived,
+ before.bytesReceived,
+ now.timestamp,
+ before.timestamp,
+ ),
+ upload: 0,
+ });
+ }
+
+ public static buildBitrateSend(trackStats: MediaTrackStats, now: any, before: any): void {
+ trackStats.setBitrate({
+ download: 0,
+ upload: this.calculateBitrate(now.bytesSent, before.bytesSent, now.timestamp, before.timestamp),
+ });
+ }
+
+ public static buildPacketsLost(trackStats: MediaTrackStats, now: any, before: any): void {
+ const key = now.type === "outbound-rtp" ? "packetsSent" : "packetsReceived";
+
+ let packetsNow = now[key];
+ if (!packetsNow || packetsNow < 0) {
+ packetsNow = 0;
+ }
+
+ const packetsBefore = StatsValueFormatter.getNonNegativeValue(before[key]);
+ const packetsDiff = Math.max(0, packetsNow - packetsBefore);
+
+ const packetsLostNow = StatsValueFormatter.getNonNegativeValue(now.packetsLost);
+ const packetsLostBefore = StatsValueFormatter.getNonNegativeValue(before.packetsLost);
+ const packetsLostDiff = Math.max(0, packetsLostNow - packetsLostBefore);
+
+ trackStats.setLoss({
+ packetsTotal: packetsDiff + packetsLostDiff,
+ packetsLost: packetsLostDiff,
+ isDownloadStream: now.type !== "outbound-rtp",
+ });
+ }
+
+ private static calculateBitrate(
+ bytesNowAny: any,
+ bytesBeforeAny: any,
+ nowTimestamp: number,
+ beforeTimestamp: number,
+ ): number {
+ const bytesNow = StatsValueFormatter.getNonNegativeValue(bytesNowAny);
+ const bytesBefore = StatsValueFormatter.getNonNegativeValue(bytesBeforeAny);
+ const bytesProcessed = Math.max(0, bytesNow - bytesBefore);
+
+ const timeMs = nowTimestamp - beforeTimestamp;
+ let bitrateKbps = 0;
+
+ if (timeMs > 0) {
+ // TODO is there any reason to round here?
+ bitrateKbps = Math.round((bytesProcessed * 8) / timeMs);
+ }
+
+ return bitrateKbps;
+ }
+}
diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats/transportStats.ts b/includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats/transportStats.ts
new file mode 100644
index 0000000..2b6e975
--- /dev/null
+++ b/includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats/transportStats.ts
@@ -0,0 +1,26 @@
+/*
+Copyright 2023 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+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 TransportStats {
+ ip: string;
+ type: string;
+ localIp: string;
+ isFocus: boolean;
+ localCandidateType: string;
+ remoteCandidateType: string;
+ networkType: string;
+ rtt: number;
+}
diff --git a/includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats/transportStatsReporter.ts b/includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats/transportStatsReporter.ts
new file mode 100644
index 0000000..d419a73
--- /dev/null
+++ b/includes/external/matrix/node_modules/matrix-js-sdk/src/webrtc/stats/transportStatsReporter.ts
@@ -0,0 +1,48 @@
+import { TransportStats } from "./transportStats";
+
+export class TransportStatsReporter {
+ public static buildReport(
+ report: RTCStatsReport | undefined,
+ now: RTCIceCandidatePairStats,
+ conferenceStatsTransport: TransportStats[],
+ isFocus: boolean,
+ ): TransportStats[] {
+ const localUsedCandidate = report?.get(now.localCandidateId);
+ const remoteUsedCandidate = report?.get(now.remoteCandidateId);
+
+ // RTCIceCandidateStats
+ // https://w3c.github.io/webrtc-stats/#icecandidate-dict*
+ if (remoteUsedCandidate && localUsedCandidate) {
+ const remoteIpAddress =
+ remoteUsedCandidate.ip !== undefined ? remoteUsedCandidate.ip : remoteUsedCandidate.address;
+ const remotePort = remoteUsedCandidate.port;
+ const ip = `${remoteIpAddress}:${remotePort}`;
+
+ const localIpAddress =
+ localUsedCandidate.ip !== undefined ? localUsedCandidate.ip : localUsedCandidate.address;
+ const localPort = localUsedCandidate.port;
+ const localIp = `${localIpAddress}:${localPort}`;
+
+ const type = remoteUsedCandidate.protocol;
+
+ // Save the address unless it has been saved already.
+ if (
+ !conferenceStatsTransport.some(
+ (t: TransportStats) => t.ip === ip && t.type === type && t.localIp === localIp,
+ )
+ ) {
+ conferenceStatsTransport.push({
+ ip,
+ type,
+ localIp,
+ isFocus,
+ localCandidateType: localUsedCandidate.candidateType,
+ remoteCandidateType: remoteUsedCandidate.candidateType,
+ networkType: localUsedCandidate.networkType,
+ rtt: now.currentRoundTripTime ? now.currentRoundTripTime * 1000 : NaN,
+ } as TransportStats);
+ }
+ }
+ return conferenceStatsTransport;
+ }
+}