@observertc/observer-js 0.41.7 → 0.42.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/ObservedCall.d.ts +10 -3
- package/lib/ObservedCall.d.ts.map +1 -1
- package/lib/ObservedCall.js +28 -0
- package/lib/ObservedClient.d.ts +4 -0
- package/lib/ObservedClient.d.ts.map +1 -1
- package/lib/ObservedClient.js +81 -32
- package/lib/ObservedInboundAudioTrack.d.ts +94 -0
- package/lib/ObservedInboundAudioTrack.d.ts.map +1 -0
- package/lib/ObservedInboundAudioTrack.js +215 -0
- package/lib/ObservedInboundVideoTrack.d.ts +98 -0
- package/lib/ObservedInboundVideoTrack.d.ts.map +1 -0
- package/lib/{ObservedInboundTrack.js → ObservedInboundVideoTrack.js} +53 -44
- package/lib/ObservedOutboundAudioTrack.d.ts +85 -0
- package/lib/ObservedOutboundAudioTrack.d.ts.map +1 -0
- package/lib/{ObservedOutboundTrack.js → ObservedOutboundAudioTrack.js} +35 -58
- package/lib/ObservedOutboundVideoTrack.d.ts +88 -0
- package/lib/ObservedOutboundVideoTrack.d.ts.map +1 -0
- package/lib/ObservedOutboundVideoTrack.js +204 -0
- package/lib/ObservedPeerConnection.d.ts +26 -16
- package/lib/ObservedPeerConnection.d.ts.map +1 -1
- package/lib/ObservedPeerConnection.js +157 -86
- package/lib/RemoteTrackAssigner.d.ts +14 -0
- package/lib/RemoteTrackAssigner.d.ts.map +1 -0
- package/lib/RemoteTrackAssigner.js +52 -0
- package/lib/common/CalculatedScore.d.ts +79 -0
- package/lib/common/CalculatedScore.d.ts.map +1 -0
- package/lib/common/CalculatedScore.js +155 -0
- package/lib/common/QualityScoreUtils.d.ts +1 -0
- package/lib/common/QualityScoreUtils.d.ts.map +1 -0
- package/lib/common/QualityScoreUtils.js +1 -0
- package/lib/common/utils.d.ts +1 -0
- package/lib/common/utils.d.ts.map +1 -1
- package/lib/common/utils.js +14 -1
- package/lib/index.d.ts +4 -2
- package/lib/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/ObservedCall.ts +40 -4
- package/src/ObservedClient.ts +77 -17
- package/src/ObservedInboundAudioTrack.ts +336 -0
- package/src/{ObservedInboundTrack.ts → ObservedInboundVideoTrack.ts} +85 -66
- package/src/{ObservedOutboundTrack.ts → ObservedOutboundAudioTrack.ts} +59 -74
- package/src/ObservedOutboundVideoTrack.ts +326 -0
- package/src/ObservedPeerConnection.ts +205 -131
- package/src/RemoteTrackAssigner.ts +68 -0
- package/src/common/CalculatedScore.ts +180 -0
- package/src/common/QualityScoreUtils.ts +0 -0
- package/src/common/utils.ts +17 -1
- package/src/index.ts +4 -2
- package/src/monitors/CallSummaryMonitor.ts +3 -3
- package/lib/ObservedInboundTrack.d.ts +0 -96
- package/lib/ObservedInboundTrack.d.ts.map +0 -1
- package/lib/ObservedOutboundTrack.d.ts +0 -84
- package/lib/ObservedOutboundTrack.d.ts.map +0 -1
|
@@ -1,28 +1,29 @@
|
|
|
1
1
|
import { EventEmitter } from 'events';
|
|
2
2
|
import { ObservedPeerConnection } from './ObservedPeerConnection';
|
|
3
|
-
import {
|
|
4
|
-
import { ObservedOutboundTrack } from './ObservedOutboundTrack';
|
|
3
|
+
import { InboundVideoTrack } from '@observertc/sample-schemas-js';
|
|
5
4
|
import { InboundAudioTrackReport, InboundVideoTrackReport } from '@observertc/report-schemas-js';
|
|
6
|
-
import {
|
|
5
|
+
import { CalculatedScore, calculateBaseVideoScore } from './common/CalculatedScore';
|
|
6
|
+
import { ClientIssue } from './monitors/CallSummary';
|
|
7
|
+
import { ObservedOutboundVideoTrack } from './ObservedOutboundVideoTrack';
|
|
7
8
|
|
|
8
|
-
export type
|
|
9
|
+
export type ObservedInboundVideoTrackModel = {
|
|
9
10
|
trackId: string;
|
|
10
|
-
kind: K;
|
|
11
11
|
sfuStreamId?: string;
|
|
12
12
|
sfuSinkId?: string;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
export type
|
|
15
|
+
export type ObservedInboundVideoTrackEvents = {
|
|
16
16
|
update: [{
|
|
17
17
|
elapsedTimeInMs: number;
|
|
18
18
|
}],
|
|
19
|
+
score: [CalculatedScore],
|
|
19
20
|
close: [],
|
|
20
|
-
remoteoutboundtrack: [
|
|
21
|
+
remoteoutboundtrack: [ObservedOutboundVideoTrack | undefined],
|
|
21
22
|
};
|
|
22
23
|
|
|
23
|
-
export type
|
|
24
|
+
export type ObservedInboundVideoTrackStatsUpdate = InboundVideoTrack;
|
|
24
25
|
|
|
25
|
-
export type
|
|
26
|
+
export type ObservedInboundVideoTrackStats = InboundVideoTrack & {
|
|
26
27
|
ssrc: number;
|
|
27
28
|
bitrate: number;
|
|
28
29
|
fractionLost: number;
|
|
@@ -33,8 +34,6 @@ export type ObservedInboundTrackStats<K extends MediaKind> = ObservedInboundTrac
|
|
|
33
34
|
deltaReceivedFrames?: number;
|
|
34
35
|
deltaDecodedFrames?: number;
|
|
35
36
|
deltaDroppedFrames?: number;
|
|
36
|
-
deltaReceivedSamples?: number;
|
|
37
|
-
deltaSilentConcealedSamples?: number;
|
|
38
37
|
fractionLoss?: number;
|
|
39
38
|
statsTimestamp: number;
|
|
40
39
|
};
|
|
@@ -43,27 +42,31 @@ export type ObservedInboundTrackStats<K extends MediaKind> = ObservedInboundTrac
|
|
|
43
42
|
// [Property in keyof ObservedInboundTrackStats<K>]: ObservedInboundTrackStats<K>[Property];
|
|
44
43
|
// }
|
|
45
44
|
|
|
46
|
-
export declare interface
|
|
47
|
-
on<U extends keyof
|
|
48
|
-
off<U extends keyof
|
|
49
|
-
once<U extends keyof
|
|
50
|
-
emit<U extends keyof
|
|
51
|
-
update(sample:
|
|
45
|
+
export declare interface ObservedInboundVideoTrack {
|
|
46
|
+
on<U extends keyof ObservedInboundVideoTrackEvents>(event: U, listener: (...args: ObservedInboundVideoTrackEvents[U]) => void): this;
|
|
47
|
+
off<U extends keyof ObservedInboundVideoTrackEvents>(event: U, listener: (...args: ObservedInboundVideoTrackEvents[U]) => void): this;
|
|
48
|
+
once<U extends keyof ObservedInboundVideoTrackEvents>(event: U, listener: (...args: ObservedInboundVideoTrackEvents[U]) => void): this;
|
|
49
|
+
emit<U extends keyof ObservedInboundVideoTrackEvents>(event: U, ...args: ObservedInboundVideoTrackEvents[U]): boolean;
|
|
50
|
+
update(sample: InboundVideoTrack, timestamp: number): void;
|
|
52
51
|
}
|
|
53
52
|
|
|
54
|
-
export class
|
|
53
|
+
export class ObservedInboundVideoTrack extends EventEmitter {
|
|
55
54
|
public readonly created = Date.now();
|
|
56
55
|
public visited = false;
|
|
57
56
|
|
|
58
|
-
|
|
57
|
+
public contentType: 'lowmotion' | 'standard' | 'highmotion' = 'standard';
|
|
58
|
+
public codec: 'vp8' | 'vp9' | 'h264' = 'vp8';
|
|
59
|
+
|
|
60
|
+
private readonly _stats = new Map<number, ObservedInboundVideoTrackStats>();
|
|
59
61
|
|
|
60
62
|
private _closed = false;
|
|
61
63
|
private _updated = Date.now();
|
|
62
|
-
private _remoteOutboundTrack?:
|
|
64
|
+
private _remoteOutboundTrack?: ObservedOutboundVideoTrack;
|
|
63
65
|
private _lastMaxStatsTimestamp = 0;
|
|
64
66
|
|
|
65
67
|
public bitrate = 0;
|
|
66
68
|
public rttInMs?: number;
|
|
69
|
+
public jitter?: number;
|
|
67
70
|
public fractionLoss = 0;
|
|
68
71
|
public marker?: string;
|
|
69
72
|
|
|
@@ -73,8 +76,6 @@ export class ObservedInboundTrack<Kind extends MediaKind> extends EventEmitter {
|
|
|
73
76
|
public totalReceivedFrames = 0;
|
|
74
77
|
public totalDecodedFrames = 0;
|
|
75
78
|
public totalDroppedFrames = 0;
|
|
76
|
-
public totalReceivedSamples = 0;
|
|
77
|
-
public totalSilentConcealedSamples = 0;
|
|
78
79
|
|
|
79
80
|
public deltaLostPackets = 0;
|
|
80
81
|
public deltaReceivedPackets = 0;
|
|
@@ -82,11 +83,13 @@ export class ObservedInboundTrack<Kind extends MediaKind> extends EventEmitter {
|
|
|
82
83
|
public deltaReceivedFrames = 0;
|
|
83
84
|
public deltaDecodedFrames = 0;
|
|
84
85
|
public deltaDroppedFrames = 0;
|
|
85
|
-
public
|
|
86
|
-
public
|
|
86
|
+
public highestLayer?: ObservedInboundVideoTrackStats;
|
|
87
|
+
public score?: CalculatedScore;
|
|
87
88
|
|
|
89
|
+
public ωpendingIssuesForScores: ClientIssue[] = [];
|
|
90
|
+
|
|
88
91
|
public constructor(
|
|
89
|
-
private readonly _model:
|
|
92
|
+
private readonly _model: ObservedInboundVideoTrackModel,
|
|
90
93
|
public readonly peerConnection: ObservedPeerConnection,
|
|
91
94
|
) {
|
|
92
95
|
super();
|
|
@@ -121,10 +124,6 @@ export class ObservedInboundTrack<Kind extends MediaKind> extends EventEmitter {
|
|
|
121
124
|
return this._model.trackId;
|
|
122
125
|
}
|
|
123
126
|
|
|
124
|
-
public get kind(): Kind {
|
|
125
|
-
return this._model.kind as Kind;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
127
|
public get sfuStreamId() {
|
|
129
128
|
return this._model.sfuStreamId;
|
|
130
129
|
}
|
|
@@ -137,7 +136,11 @@ export class ObservedInboundTrack<Kind extends MediaKind> extends EventEmitter {
|
|
|
137
136
|
return this._updated;
|
|
138
137
|
}
|
|
139
138
|
|
|
140
|
-
public get
|
|
139
|
+
public get statsTimestamp() {
|
|
140
|
+
return this._lastMaxStatsTimestamp;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
public get stats(): ReadonlyMap<number, ObservedInboundVideoTrackStats> {
|
|
141
144
|
return this._stats;
|
|
142
145
|
}
|
|
143
146
|
|
|
@@ -145,12 +148,14 @@ export class ObservedInboundTrack<Kind extends MediaKind> extends EventEmitter {
|
|
|
145
148
|
return this.peerConnection.reports;
|
|
146
149
|
}
|
|
147
150
|
|
|
148
|
-
public set remoteOutboundTrack(track:
|
|
151
|
+
public set remoteOutboundTrack(track: ObservedOutboundVideoTrack | undefined) {
|
|
149
152
|
if (this._closed) return;
|
|
150
153
|
if (this._remoteOutboundTrack) return;
|
|
151
154
|
if (!track) return;
|
|
152
|
-
|
|
155
|
+
|
|
156
|
+
track.once('close', () => {
|
|
153
157
|
this._remoteOutboundTrack = undefined;
|
|
158
|
+
this.emit('remoteoutboundtrack', undefined);
|
|
154
159
|
});
|
|
155
160
|
this._remoteOutboundTrack = track;
|
|
156
161
|
this.emit('remoteoutboundtrack', track);
|
|
@@ -172,7 +177,7 @@ export class ObservedInboundTrack<Kind extends MediaKind> extends EventEmitter {
|
|
|
172
177
|
this.emit('close');
|
|
173
178
|
}
|
|
174
179
|
|
|
175
|
-
public update(sample:
|
|
180
|
+
public update(sample: ObservedInboundVideoTrackStatsUpdate, statsTimestamp: number) {
|
|
176
181
|
if (this._closed) return;
|
|
177
182
|
|
|
178
183
|
const now = Date.now();
|
|
@@ -195,8 +200,7 @@ export class ObservedInboundTrack<Kind extends MediaKind> extends EventEmitter {
|
|
|
195
200
|
marker: this.marker,
|
|
196
201
|
};
|
|
197
202
|
|
|
198
|
-
|
|
199
|
-
else if (this.kind === 'video') this.reports.addInboundVideoTrackReport(report);
|
|
203
|
+
this.reports.addInboundVideoTrackReport(report);
|
|
200
204
|
|
|
201
205
|
if (!this._model.sfuStreamId && sample.sfuStreamId) {
|
|
202
206
|
this._model.sfuStreamId = sample.sfuStreamId;
|
|
@@ -232,31 +236,20 @@ export class ObservedInboundTrack<Kind extends MediaKind> extends EventEmitter {
|
|
|
232
236
|
let deltaDecodedFrames: number | undefined;
|
|
233
237
|
let deltaDroppedFrames: number | undefined;
|
|
234
238
|
let deltaReceivedFrames: number | undefined;
|
|
235
|
-
|
|
239
|
+
const videoSample = sample as InboundVideoTrack;
|
|
240
|
+
const lastVideoStat = lastStat as InboundVideoTrack | undefined;
|
|
236
241
|
|
|
237
|
-
if (
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
deltaDroppedFrames = videoSample.framesDropped - lastVideoStat.framesDropped;
|
|
246
|
-
}
|
|
247
|
-
if (videoSample.framesReceived && lastVideoStat?.framesReceived && lastVideoStat.framesReceived < videoSample.framesReceived) {
|
|
248
|
-
deltaReceivedFrames = videoSample.framesReceived - lastVideoStat.framesReceived;
|
|
249
|
-
}
|
|
250
|
-
} else if (this.kind === 'audio') {
|
|
251
|
-
const audioSample = sample as InboundAudioTrack;
|
|
252
|
-
const lastAudioStat = lastStat as InboundAudioTrack | undefined;
|
|
253
|
-
|
|
254
|
-
if (audioSample.silentConcealedSamples && lastAudioStat?.silentConcealedSamples && lastAudioStat.silentConcealedSamples < audioSample.silentConcealedSamples) {
|
|
255
|
-
deltaSilentConcealedSamples = audioSample.silentConcealedSamples - lastAudioStat.silentConcealedSamples;
|
|
256
|
-
}
|
|
242
|
+
if (videoSample.framesDecoded && lastVideoStat?.framesDecoded && lastVideoStat.framesDecoded < videoSample.framesDecoded) {
|
|
243
|
+
deltaDecodedFrames = videoSample.framesDecoded - lastVideoStat.framesDecoded;
|
|
244
|
+
}
|
|
245
|
+
if (videoSample.framesDropped && lastVideoStat?.framesDropped && lastVideoStat.framesDropped < videoSample.framesDropped) {
|
|
246
|
+
deltaDroppedFrames = videoSample.framesDropped - lastVideoStat.framesDropped;
|
|
247
|
+
}
|
|
248
|
+
if (videoSample.framesReceived && lastVideoStat?.framesReceived && lastVideoStat.framesReceived < videoSample.framesReceived) {
|
|
249
|
+
deltaReceivedFrames = videoSample.framesReceived - lastVideoStat.framesReceived;
|
|
257
250
|
}
|
|
258
251
|
|
|
259
|
-
const stats:
|
|
252
|
+
const stats: ObservedInboundVideoTrackStats = {
|
|
260
253
|
...sample,
|
|
261
254
|
fractionLost,
|
|
262
255
|
rttInMs,
|
|
@@ -268,7 +261,6 @@ export class ObservedInboundTrack<Kind extends MediaKind> extends EventEmitter {
|
|
|
268
261
|
deltaReceivedFrames,
|
|
269
262
|
deltaDecodedFrames,
|
|
270
263
|
deltaDroppedFrames,
|
|
271
|
-
deltaSilentConcealedSamples,
|
|
272
264
|
statsTimestamp,
|
|
273
265
|
};
|
|
274
266
|
|
|
@@ -284,6 +276,7 @@ export class ObservedInboundTrack<Kind extends MediaKind> extends EventEmitter {
|
|
|
284
276
|
public updateMetrics() {
|
|
285
277
|
let maxStatsTimestamp = 0;
|
|
286
278
|
let rttInMsSum = 0;
|
|
279
|
+
let jitterSum = 0;
|
|
287
280
|
let size = 0;
|
|
288
281
|
|
|
289
282
|
this.bitrate = 0;
|
|
@@ -293,11 +286,11 @@ export class ObservedInboundTrack<Kind extends MediaKind> extends EventEmitter {
|
|
|
293
286
|
this.deltaReceivedFrames = 0;
|
|
294
287
|
this.deltaDecodedFrames = 0;
|
|
295
288
|
this.deltaDroppedFrames = 0;
|
|
296
|
-
this.deltaReceivedSamples = 0;
|
|
297
|
-
this.deltaSilentConcealedSamples = 0;
|
|
298
289
|
this.fractionLoss = 0;
|
|
299
290
|
|
|
300
|
-
|
|
291
|
+
const highestLayer = { ssrc: -1, bitrate: -1 };
|
|
292
|
+
|
|
293
|
+
for (const [ ssrc, stats ] of this._stats) {
|
|
301
294
|
if (stats.statsTimestamp <= this._lastMaxStatsTimestamp) continue;
|
|
302
295
|
|
|
303
296
|
this.deltaBytesReceived += stats.deltaReceivedBytes ?? 0;
|
|
@@ -306,28 +299,54 @@ export class ObservedInboundTrack<Kind extends MediaKind> extends EventEmitter {
|
|
|
306
299
|
this.deltaReceivedFrames += stats.deltaReceivedFrames ?? 0;
|
|
307
300
|
this.deltaDecodedFrames += stats.deltaDecodedFrames ?? 0;
|
|
308
301
|
this.deltaDroppedFrames += stats.deltaDroppedFrames ?? 0;
|
|
309
|
-
this.deltaReceivedSamples += stats.deltaReceivedSamples ?? 0;
|
|
310
|
-
this.deltaSilentConcealedSamples += stats.deltaSilentConcealedSamples ?? 0;
|
|
311
302
|
this.bitrate += stats.bitrate;
|
|
312
303
|
|
|
313
304
|
maxStatsTimestamp = Math.max(maxStatsTimestamp, stats.statsTimestamp);
|
|
314
305
|
|
|
306
|
+
if (highestLayer.bitrate < stats.bitrate) {
|
|
307
|
+
highestLayer.ssrc = ssrc;
|
|
308
|
+
}
|
|
315
309
|
rttInMsSum += stats.rttInMs ?? 0;
|
|
310
|
+
jitterSum += stats.jitter ?? 0;
|
|
316
311
|
++size;
|
|
317
312
|
}
|
|
318
|
-
|
|
313
|
+
this.highestLayer = this.stats.get(highestLayer.ssrc);
|
|
319
314
|
this.totalBytesReceived += this.deltaBytesReceived;
|
|
320
315
|
this.totalLostPackets += this.deltaLostPackets;
|
|
321
316
|
this.totalReceivedPackets += this.deltaReceivedPackets;
|
|
322
317
|
this.totalReceivedFrames += this.deltaReceivedFrames;
|
|
323
318
|
this.totalDecodedFrames += this.deltaDecodedFrames;
|
|
324
319
|
this.totalDroppedFrames += this.deltaDroppedFrames;
|
|
325
|
-
this.totalReceivedSamples += this.deltaReceivedSamples;
|
|
326
|
-
this.totalSilentConcealedSamples += this.deltaSilentConcealedSamples;
|
|
327
320
|
|
|
328
321
|
this.rttInMs = rttInMsSum / Math.max(size, 1);
|
|
322
|
+
this.jitter = jitterSum / Math.max(size, 1);
|
|
329
323
|
this.fractionLoss = 0 < this.deltaReceivedPackets && 0 < this.deltaLostPackets ? (this.deltaLostPackets / (this.deltaReceivedPackets + this.deltaLostPackets)) : 0;
|
|
330
324
|
|
|
331
325
|
this._lastMaxStatsTimestamp = maxStatsTimestamp;
|
|
326
|
+
this._updateQualityScore(maxStatsTimestamp);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
private _updateQualityScore(timestamp: number) {
|
|
330
|
+
const newIssues = this.ωpendingIssuesForScores;
|
|
331
|
+
const score = calculateBaseVideoScore(this, newIssues);
|
|
332
|
+
|
|
333
|
+
if (0 < this.ωpendingIssuesForScores.length) {
|
|
334
|
+
this.ωpendingIssuesForScores = [];
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (!score) return (this.score = undefined);
|
|
338
|
+
|
|
339
|
+
if (0.05 < this.fractionLoss) {
|
|
340
|
+
score.score *= 0.5;
|
|
341
|
+
score.remarks.push({
|
|
342
|
+
severity: 'major',
|
|
343
|
+
text: 'Fraction loss is too high',
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
score.timestamp = timestamp;
|
|
348
|
+
this.score = score;
|
|
349
|
+
|
|
350
|
+
this.emit('score', this.score);
|
|
332
351
|
}
|
|
333
352
|
}
|
|
@@ -1,27 +1,26 @@
|
|
|
1
1
|
import { EventEmitter } from 'events';
|
|
2
2
|
import { ObservedPeerConnection } from './ObservedPeerConnection';
|
|
3
3
|
import { OutboundAudioTrack, OutboundVideoTrack } from '@observertc/sample-schemas-js';
|
|
4
|
-
import { ObservedInboundTrack } from './ObservedInboundTrack';
|
|
5
4
|
import { OutboundAudioTrackReport, OutboundVideoTrackReport } from '@observertc/report-schemas-js';
|
|
6
|
-
import {
|
|
5
|
+
import { ObservedInboundAudioTrack } from './ObservedInboundAudioTrack';
|
|
6
|
+
import { calculateBaseAudioScore, CalculatedScore } from './common/CalculatedScore';
|
|
7
|
+
import { ClientIssue } from './monitors/CallSummary';
|
|
7
8
|
|
|
8
|
-
export type
|
|
9
|
+
export type ObservedOutboundAudioTrackModel = {
|
|
9
10
|
trackId: string;
|
|
10
|
-
kind: K;
|
|
11
11
|
sfuStreamId?: string;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
export type
|
|
14
|
+
export type ObservedOutboundAudioTrackEvents = {
|
|
15
15
|
qualitylimitationchanged: [string];
|
|
16
16
|
update: [{
|
|
17
17
|
elapsedTimeInMs: number;
|
|
18
18
|
}],
|
|
19
|
+
score: [CalculatedScore],
|
|
19
20
|
close: [],
|
|
20
21
|
};
|
|
21
22
|
|
|
22
|
-
export type
|
|
23
|
-
|
|
24
|
-
export type ObservedOutboundTrackStats<K extends MediaKind> = ObservedOutboundTrackStatsUpdate<K> & {
|
|
23
|
+
export type ObservedOutboundAudioTrackStats = OutboundAudioTrack & {
|
|
25
24
|
ssrc: number;
|
|
26
25
|
bitrate: number;
|
|
27
26
|
rttInMs?: number;
|
|
@@ -35,24 +34,25 @@ export type ObservedOutboundTrackStats<K extends MediaKind> = ObservedOutboundTr
|
|
|
35
34
|
statsTimestamp: number;
|
|
36
35
|
};
|
|
37
36
|
|
|
38
|
-
// export type ObservedOutboundTrackStatsUpdate
|
|
37
|
+
// export type ObservedOutboundTrackStatsUpdate = {
|
|
39
38
|
// [Property in keyof ObservedOutboundTrackStats<K>]: ObservedOutboundTrackStats<K>[Property];
|
|
40
39
|
// }
|
|
41
40
|
|
|
42
|
-
export declare interface
|
|
43
|
-
on<U extends keyof
|
|
44
|
-
off<U extends keyof
|
|
45
|
-
once<U extends keyof
|
|
46
|
-
emit<U extends keyof
|
|
47
|
-
update(sample:
|
|
41
|
+
export declare interface ObservedOutboundAudioTrack {
|
|
42
|
+
on<U extends keyof ObservedOutboundAudioTrackEvents>(event: U, listener: (...args: ObservedOutboundAudioTrackEvents[U]) => void): this;
|
|
43
|
+
off<U extends keyof ObservedOutboundAudioTrackEvents>(event: U, listener: (...args: ObservedOutboundAudioTrackEvents[U]) => void): this;
|
|
44
|
+
once<U extends keyof ObservedOutboundAudioTrackEvents>(event: U, listener: (...args: ObservedOutboundAudioTrackEvents[U]) => void): this;
|
|
45
|
+
emit<U extends keyof ObservedOutboundAudioTrackEvents>(event: U, ...args: ObservedOutboundAudioTrackEvents[U]): boolean;
|
|
46
|
+
update(sample: OutboundAudioTrack, timestamp: number): void;
|
|
48
47
|
}
|
|
49
48
|
|
|
50
|
-
export class
|
|
49
|
+
export class ObservedOutboundAudioTrack extends EventEmitter {
|
|
51
50
|
public readonly created = Date.now();
|
|
52
51
|
public visited = false;
|
|
53
52
|
|
|
54
53
|
public bitrate = 0;
|
|
55
54
|
public rttInMs?: number;
|
|
55
|
+
public jitter?: number;
|
|
56
56
|
public marker?: string;
|
|
57
57
|
|
|
58
58
|
public totalLostPackets = 0;
|
|
@@ -68,21 +68,24 @@ export class ObservedOutboundTrack<Kind extends MediaKind> extends EventEmitter
|
|
|
68
68
|
|
|
69
69
|
public sendingBitrate = 0;
|
|
70
70
|
|
|
71
|
-
private readonly _stats = new Map<number,
|
|
71
|
+
private readonly _stats = new Map<number, ObservedOutboundAudioTrackStats>();
|
|
72
72
|
private _lastMaxStatsTimestamp = 0;
|
|
73
73
|
|
|
74
74
|
private _closed = false;
|
|
75
75
|
private _updated = Date.now();
|
|
76
|
-
private
|
|
76
|
+
private _lastUpdateMetrics?: number;
|
|
77
|
+
|
|
78
|
+
public score?: CalculatedScore;
|
|
79
|
+
public ωpendingIssuesForScores: ClientIssue[] = [];
|
|
80
|
+
|
|
81
|
+
public readonly remoteInboundTracks = new Map<string, ObservedInboundAudioTrack>();
|
|
77
82
|
|
|
78
83
|
public constructor(
|
|
79
|
-
private readonly _model:
|
|
84
|
+
private readonly _model: ObservedOutboundAudioTrackModel,
|
|
80
85
|
public readonly peerConnection: ObservedPeerConnection,
|
|
81
86
|
) {
|
|
82
87
|
super();
|
|
83
88
|
this.setMaxListeners(Infinity);
|
|
84
|
-
|
|
85
|
-
if (this._model.sfuStreamId) this._assignSfuStreamId(this._model.sfuStreamId);
|
|
86
89
|
}
|
|
87
90
|
|
|
88
91
|
public get serviceId() {
|
|
@@ -109,10 +112,6 @@ export class ObservedOutboundTrack<Kind extends MediaKind> extends EventEmitter
|
|
|
109
112
|
return this.peerConnection.peerConnectionId;
|
|
110
113
|
}
|
|
111
114
|
|
|
112
|
-
public get kind(): Kind {
|
|
113
|
-
return this._model.kind as Kind;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
115
|
public get trackId() {
|
|
117
116
|
return this._model.trackId;
|
|
118
117
|
}
|
|
@@ -121,19 +120,19 @@ export class ObservedOutboundTrack<Kind extends MediaKind> extends EventEmitter
|
|
|
121
120
|
return this._model.sfuStreamId;
|
|
122
121
|
}
|
|
123
122
|
|
|
124
|
-
public get remoteInboundTracks(): ReadonlyMap<string, ObservedInboundTrack<Kind>> {
|
|
125
|
-
return this._remoteInboundTracks;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
123
|
public get reports() {
|
|
129
124
|
return this.peerConnection.reports;
|
|
130
125
|
}
|
|
131
126
|
|
|
127
|
+
public get statsTimestamp() {
|
|
128
|
+
return this._lastMaxStatsTimestamp;
|
|
129
|
+
}
|
|
130
|
+
|
|
132
131
|
public get updated() {
|
|
133
132
|
return this._updated;
|
|
134
133
|
}
|
|
135
134
|
|
|
136
|
-
public get stats(): ReadonlyMap<number,
|
|
135
|
+
public get stats(): ReadonlyMap<number, ObservedOutboundAudioTrackStats> {
|
|
137
136
|
return this._stats;
|
|
138
137
|
}
|
|
139
138
|
|
|
@@ -148,7 +147,7 @@ export class ObservedOutboundTrack<Kind extends MediaKind> extends EventEmitter
|
|
|
148
147
|
this.emit('close');
|
|
149
148
|
}
|
|
150
149
|
|
|
151
|
-
public update(sample:
|
|
150
|
+
public update(sample: OutboundAudioTrack, statsTimestamp: number): void {
|
|
152
151
|
if (this._closed) return;
|
|
153
152
|
|
|
154
153
|
const now = Date.now();
|
|
@@ -166,8 +165,7 @@ export class ObservedOutboundTrack<Kind extends MediaKind> extends EventEmitter
|
|
|
166
165
|
marker: this.marker,
|
|
167
166
|
};
|
|
168
167
|
|
|
169
|
-
|
|
170
|
-
else if (this.kind === 'video') this.reports.addOutboundVideoTrackReport(report);
|
|
168
|
+
this.reports.addOutboundAudioTrackReport(report);
|
|
171
169
|
|
|
172
170
|
const elapsedTimeInMs = Math.max(1, now - this._updated);
|
|
173
171
|
const lastStat = this._stats.get(sample.ssrc);
|
|
@@ -193,26 +191,21 @@ export class ObservedOutboundTrack<Kind extends MediaKind> extends EventEmitter
|
|
|
193
191
|
let deltaEncodedFrames: number | undefined;
|
|
194
192
|
let deltaSentFrames: number | undefined;
|
|
195
193
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
const lastVideoStats = lastStat as OutboundVideoTrack | undefined;
|
|
194
|
+
const videoSample = sample as OutboundVideoTrack;
|
|
195
|
+
const lastVideoStats = lastStat as OutboundVideoTrack | undefined;
|
|
199
196
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
if (videoSample.qualityLimitationReason && lastVideoStats?.qualityLimitationReason !== videoSample.qualityLimitationReason) {
|
|
208
|
-
this.emit('qualitylimitationchanged', videoSample.qualityLimitationReason);
|
|
209
|
-
}
|
|
210
|
-
} else if (this.kind === 'audio') {
|
|
211
|
-
// const audioSample = sample as OutboundAudioTrack;
|
|
212
|
-
// const lastAudioStats = lastStat as OutboundAudioTrack | undefined;
|
|
197
|
+
if (videoSample?.framesEncoded && lastVideoStats?.framesEncoded && lastVideoStats.framesEncoded < videoSample.framesEncoded) {
|
|
198
|
+
deltaEncodedFrames = videoSample.framesEncoded - lastVideoStats.framesEncoded;
|
|
199
|
+
}
|
|
200
|
+
if (videoSample?.framesSent && lastVideoStats?.framesSent && lastVideoStats.framesSent < videoSample.framesSent) {
|
|
201
|
+
deltaSentFrames = videoSample.framesSent - lastVideoStats.framesSent;
|
|
202
|
+
}
|
|
213
203
|
|
|
204
|
+
if (videoSample.qualityLimitationReason && lastVideoStats?.qualityLimitationReason !== videoSample.qualityLimitationReason) {
|
|
205
|
+
this.emit('qualitylimitationchanged', videoSample.qualityLimitationReason);
|
|
214
206
|
}
|
|
215
|
-
|
|
207
|
+
|
|
208
|
+
const stats: ObservedOutboundAudioTrackStats = {
|
|
216
209
|
...sample,
|
|
217
210
|
rttInMs,
|
|
218
211
|
bitrate,
|
|
@@ -244,9 +237,6 @@ export class ObservedOutboundTrack<Kind extends MediaKind> extends EventEmitter
|
|
|
244
237
|
// this.deltaSentBytes = [ ...this._stats.values() ].reduce((acc, stat) => acc + stat.deltaSentBytes, 0);
|
|
245
238
|
|
|
246
239
|
// setting up sfu connection as it is not always available at the first sample
|
|
247
|
-
if (sample.sfuStreamId && !this._model.sfuStreamId) {
|
|
248
|
-
this._assignSfuStreamId(sample.sfuStreamId);
|
|
249
|
-
}
|
|
250
240
|
|
|
251
241
|
this.visited = true;
|
|
252
242
|
this._updated = now;
|
|
@@ -255,21 +245,10 @@ export class ObservedOutboundTrack<Kind extends MediaKind> extends EventEmitter
|
|
|
255
245
|
});
|
|
256
246
|
}
|
|
257
247
|
|
|
258
|
-
public connectInboundTrack(track: ObservedInboundTrack<Kind>) {
|
|
259
|
-
if (this._closed) return;
|
|
260
|
-
|
|
261
|
-
track.remoteOutboundTrack = this;
|
|
262
|
-
track.once('close', () => {
|
|
263
|
-
this._remoteInboundTracks.delete(track.trackId);
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
this._remoteInboundTracks.set(track.trackId, track);
|
|
267
|
-
}
|
|
268
|
-
private _lastUpdateMetrics?: number;
|
|
269
|
-
|
|
270
248
|
public updateMetrics() {
|
|
271
249
|
let maxStatsTimestamp = 0;
|
|
272
250
|
let rttInMsSum = 0;
|
|
251
|
+
let jitterSum = 0;
|
|
273
252
|
let size = 0;
|
|
274
253
|
|
|
275
254
|
this.bitrate = 0;
|
|
@@ -295,6 +274,7 @@ export class ObservedOutboundTrack<Kind extends MediaKind> extends EventEmitter
|
|
|
295
274
|
maxStatsTimestamp = Math.max(maxStatsTimestamp, stats.statsTimestamp);
|
|
296
275
|
|
|
297
276
|
rttInMsSum += stats.rttInMs ?? 0;
|
|
277
|
+
jitterSum += stats.jitter ?? 0;
|
|
298
278
|
++size;
|
|
299
279
|
}
|
|
300
280
|
|
|
@@ -311,20 +291,25 @@ export class ObservedOutboundTrack<Kind extends MediaKind> extends EventEmitter
|
|
|
311
291
|
this.totalSentFrames += this.deltaSentFrames;
|
|
312
292
|
|
|
313
293
|
this.rttInMs = rttInMsSum / Math.max(size, 1);
|
|
294
|
+
this.jitter = jitterSum / Math.max(size, 1);
|
|
314
295
|
|
|
315
296
|
this._lastMaxStatsTimestamp = maxStatsTimestamp;
|
|
297
|
+
this._updateQualityScore(maxStatsTimestamp);
|
|
316
298
|
}
|
|
317
299
|
|
|
318
|
-
private
|
|
319
|
-
|
|
300
|
+
private _updateQualityScore(timestamp: number) {
|
|
301
|
+
const newIssues = this.ωpendingIssuesForScores;
|
|
302
|
+
const score = calculateBaseAudioScore(this, newIssues);
|
|
303
|
+
|
|
304
|
+
if (0 < newIssues.length) {
|
|
305
|
+
this.ωpendingIssuesForScores = [];
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (!score) return (this.score = undefined);
|
|
320
309
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
if (this.kind === 'audio') this.peerConnection.client.call.sfuStreamIdToOutboundAudioTrack.delete(this._model.sfuStreamId);
|
|
324
|
-
else if (this.kind === 'video') this.peerConnection.client.call.sfuStreamIdToOutboundVideoTrack.delete(this._model.sfuStreamId);
|
|
325
|
-
});
|
|
310
|
+
score.timestamp = timestamp;
|
|
311
|
+
this.score = score;
|
|
326
312
|
|
|
327
|
-
|
|
328
|
-
else if (this.kind === 'video') this.peerConnection.client.call.sfuStreamIdToOutboundVideoTrack.set(this._model.sfuStreamId, this as ObservedOutboundTrack<'video'>);
|
|
313
|
+
this.emit('score', this.score);
|
|
329
314
|
}
|
|
330
315
|
}
|