summaryrefslogtreecommitdiffstats
path: root/comm/chat/protocols/matrix/lib/matrix-sdk/webrtc/stats/trackStatsBuilder.js
blob: 563a14b784d731932c69389efbf98ce0aba0e075 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.TrackStatsBuilder = void 0;
var _valueFormatter = require("./valueFormatter");
class TrackStatsBuilder {
  static buildFramerateResolution(trackStats, now) {
    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));
  }
  static calculateSimulcastFramerate(trackStats, now, before, layer) {
    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);
  }
  static buildCodec(report, trackStats, now) {
    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);
    }
  }
  static buildBitrateReceived(trackStats, now, before) {
    trackStats.setBitrate({
      download: TrackStatsBuilder.calculateBitrate(now.bytesReceived, before.bytesReceived, now.timestamp, before.timestamp),
      upload: 0
    });
  }
  static buildBitrateSend(trackStats, now, before) {
    trackStats.setBitrate({
      download: 0,
      upload: this.calculateBitrate(now.bytesSent, before.bytesSent, now.timestamp, before.timestamp)
    });
  }
  static buildPacketsLost(trackStats, now, before) {
    const key = now.type === "outbound-rtp" ? "packetsSent" : "packetsReceived";
    let packetsNow = now[key];
    if (!packetsNow || packetsNow < 0) {
      packetsNow = 0;
    }
    const packetsBefore = _valueFormatter.ValueFormatter.getNonNegativeValue(before[key]);
    const packetsDiff = Math.max(0, packetsNow - packetsBefore);
    const packetsLostNow = _valueFormatter.ValueFormatter.getNonNegativeValue(now.packetsLost);
    const packetsLostBefore = _valueFormatter.ValueFormatter.getNonNegativeValue(before.packetsLost);
    const packetsLostDiff = Math.max(0, packetsLostNow - packetsLostBefore);
    trackStats.setLoss({
      packetsTotal: packetsDiff + packetsLostDiff,
      packetsLost: packetsLostDiff,
      isDownloadStream: now.type !== "outbound-rtp"
    });
  }
  static calculateBitrate(bytesNowAny, bytesBeforeAny, nowTimestamp, beforeTimestamp) {
    const bytesNow = _valueFormatter.ValueFormatter.getNonNegativeValue(bytesNowAny);
    const bytesBefore = _valueFormatter.ValueFormatter.getNonNegativeValue(bytesBeforeAny);
    const bytesProcessed = Math.max(0, bytesNow - bytesBefore);
    const timeMs = nowTimestamp - beforeTimestamp;
    let bitrateKbps = 0;
    if (timeMs > 0) {
      bitrateKbps = Math.round(bytesProcessed * 8 / timeMs);
    }
    return bitrateKbps;
  }
  static setTrackStatsState(trackStats, transceiver) {
    if (transceiver === undefined) {
      trackStats.alive = false;
      return;
    }
    const track = trackStats.getType() === "remote" ? transceiver.receiver.track : transceiver?.sender?.track;
    if (track === undefined || track === null) {
      trackStats.alive = false;
      return;
    }
    if (track.readyState === "ended") {
      trackStats.alive = false;
      return;
    }
    trackStats.muted = track.muted;
    trackStats.enabled = track.enabled;
    trackStats.alive = true;
  }
  static buildTrackSummary(trackStatsList) {
    const videoTrackSummary = {
      count: 0,
      muted: 0,
      maxJitter: 0,
      maxPacketLoss: 0,
      concealedAudio: 0,
      totalAudio: 0
    };
    const audioTrackSummary = {
      count: 0,
      muted: 0,
      maxJitter: 0,
      maxPacketLoss: 0,
      concealedAudio: 0,
      totalAudio: 0
    };
    const remoteTrackList = trackStatsList.filter(t => t.getType() === "remote");
    const audioTrackList = remoteTrackList.filter(t => t.kind === "audio");
    remoteTrackList.forEach(stats => {
      const trackSummary = stats.kind === "video" ? videoTrackSummary : audioTrackSummary;
      trackSummary.count++;
      if (stats.alive && stats.muted) {
        trackSummary.muted++;
      }
      if (trackSummary.maxJitter < stats.getJitter()) {
        trackSummary.maxJitter = stats.getJitter();
      }
      if (trackSummary.maxPacketLoss < stats.getLoss().packetsLost) {
        trackSummary.maxPacketLoss = stats.getLoss().packetsLost;
      }
      if (audioTrackList.length > 0) {
        trackSummary.concealedAudio += stats.getAudioConcealment()?.concealedAudio;
        trackSummary.totalAudio += stats.getAudioConcealment()?.totalAudioDuration;
      }
    });
    return {
      audioTrackSummary,
      videoTrackSummary
    };
  }
  static buildJitter(trackStats, statsReport) {
    if (statsReport.type !== "inbound-rtp") {
      return;
    }
    const jitterStr = statsReport?.jitter;
    if (jitterStr !== undefined) {
      const jitter = _valueFormatter.ValueFormatter.getNonNegativeValue(jitterStr);
      trackStats.setJitter(Math.round(jitter * 1000));
    } else {
      trackStats.setJitter(-1);
    }
  }
  static buildAudioConcealment(trackStats, statsReport) {
    if (statsReport.type !== "inbound-rtp") {
      return;
    }
    const msPerSample = 1000 * statsReport?.totalSamplesDuration / statsReport?.totalSamplesReceived;
    const concealedAudioDuration = msPerSample * statsReport?.concealedSamples;
    const totalAudioDuration = 1000 * statsReport?.totalSamplesDuration;
    trackStats.setAudioConcealment(concealedAudioDuration, totalAudioDuration);
  }
}
exports.TrackStatsBuilder = TrackStatsBuilder;