@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,22 +1,27 @@
|
|
|
1
1
|
import { EventEmitter } from 'events';
|
|
2
2
|
import { PeerConnectionTransport } from '@observertc/sample-schemas-js';
|
|
3
3
|
import { ObservedClient } from './ObservedClient';
|
|
4
|
-
import { ObservedInboundTrack, ObservedInboundTrackModel } from './ObservedInboundTrack';
|
|
5
|
-
import { ObservedOutboundTrack, ObservedOutboundTrackModel } from './ObservedOutboundTrack';
|
|
6
4
|
import { CallEventReport, PeerConnectionTransportReport } from '@observertc/report-schemas-js';
|
|
7
5
|
import { ObservedICE } from './ObservedICE';
|
|
8
6
|
import { ObservedDataChannel } from './ObservedDataChannel';
|
|
9
7
|
import { PartialBy } from './common/utils';
|
|
8
|
+
import { ObservedInboundAudioTrack, ObservedInboundAudioTrackModel } from './ObservedInboundAudioTrack';
|
|
9
|
+
import { ObservedInboundVideoTrack, ObservedInboundVideoTrackModel } from './ObservedInboundVideoTrack';
|
|
10
|
+
import { ObservedOutboundAudioTrack, ObservedOutboundAudioTrackModel } from './ObservedOutboundAudioTrack';
|
|
11
|
+
import { ObservedOutboundVideoTrack, ObservedOutboundVideoTrackModel } from './ObservedOutboundVideoTrack';
|
|
12
|
+
import { CalculatedScore, calculateLatencyMOS } from './common/CalculatedScore';
|
|
13
|
+
import { ClientIssue } from './monitors/CallSummary';
|
|
10
14
|
|
|
11
15
|
export type ObservedPeerConnectionEvents = {
|
|
12
16
|
update: [{
|
|
13
17
|
elapsedTimeInMs: number;
|
|
14
18
|
}],
|
|
15
19
|
close: [],
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
score: [CalculatedScore],
|
|
21
|
+
newinboudaudiotrack: [ObservedInboundAudioTrack],
|
|
22
|
+
newinboudvideotrack: [ObservedInboundVideoTrack],
|
|
23
|
+
newoutboundaudiotrack: [ObservedOutboundAudioTrack],
|
|
24
|
+
newoutboundvideotrack: [ObservedOutboundVideoTrack],
|
|
20
25
|
newdatachannel: [ObservedDataChannel],
|
|
21
26
|
};
|
|
22
27
|
|
|
@@ -39,7 +44,11 @@ export class ObservedPeerConnection extends EventEmitter {
|
|
|
39
44
|
public visited = true;
|
|
40
45
|
|
|
41
46
|
private _elapsedTimeSinceLastUpdate?: number;
|
|
47
|
+
private _statsTimestamp?: number;
|
|
48
|
+
|
|
49
|
+
public ωpendingIssuesForScores: ClientIssue[] = [];
|
|
42
50
|
|
|
51
|
+
public score?: CalculatedScore;
|
|
43
52
|
public totalInboundPacketsLost = 0;
|
|
44
53
|
public totalInboundPacketsReceived = 0;
|
|
45
54
|
public totalOutboundPacketsSent = 0;
|
|
@@ -77,6 +86,7 @@ export class ObservedPeerConnection extends EventEmitter {
|
|
|
77
86
|
public receivingVideoBitrate = 0;
|
|
78
87
|
|
|
79
88
|
public avgRttInMs?: number;
|
|
89
|
+
public avgJitter?: number;
|
|
80
90
|
|
|
81
91
|
private _closed = false;
|
|
82
92
|
private _updated = Date.now();
|
|
@@ -84,10 +94,10 @@ export class ObservedPeerConnection extends EventEmitter {
|
|
|
84
94
|
private _marker?: string;
|
|
85
95
|
|
|
86
96
|
public readonly ICE = ObservedICE.create(this);
|
|
87
|
-
private readonly _inboundAudioTracks = new Map<string,
|
|
88
|
-
private readonly _inboundVideoTracks = new Map<string,
|
|
89
|
-
private readonly _outboundAudioTracks = new Map<string,
|
|
90
|
-
private readonly _outboundVideoTracks = new Map<string,
|
|
97
|
+
private readonly _inboundAudioTracks = new Map<string, ObservedInboundAudioTrack>();
|
|
98
|
+
private readonly _inboundVideoTracks = new Map<string, ObservedInboundVideoTrack>();
|
|
99
|
+
private readonly _outboundAudioTracks = new Map<string, ObservedOutboundAudioTrack>();
|
|
100
|
+
private readonly _outboundVideoTracks = new Map<string, ObservedOutboundVideoTrack>();
|
|
91
101
|
private readonly _dataChannels = new Map<number, ObservedDataChannel>();
|
|
92
102
|
|
|
93
103
|
public constructor(
|
|
@@ -155,19 +165,19 @@ export class ObservedPeerConnection extends EventEmitter {
|
|
|
155
165
|
return this._updated;
|
|
156
166
|
}
|
|
157
167
|
|
|
158
|
-
public get inboundAudioTracks(): ReadonlyMap<string,
|
|
168
|
+
public get inboundAudioTracks(): ReadonlyMap<string, ObservedInboundAudioTrack> {
|
|
159
169
|
return this._inboundAudioTracks;
|
|
160
170
|
}
|
|
161
171
|
|
|
162
|
-
public get inboundVideoTracks(): ReadonlyMap<string,
|
|
172
|
+
public get inboundVideoTracks(): ReadonlyMap<string, ObservedInboundVideoTrack> {
|
|
163
173
|
return this._inboundVideoTracks;
|
|
164
174
|
}
|
|
165
175
|
|
|
166
|
-
public get outboundAudioTracks(): ReadonlyMap<string,
|
|
176
|
+
public get outboundAudioTracks(): ReadonlyMap<string, ObservedOutboundAudioTrack> {
|
|
167
177
|
return this._outboundAudioTracks;
|
|
168
178
|
}
|
|
169
179
|
|
|
170
|
-
public get outboundVideoTracks(): ReadonlyMap<string,
|
|
180
|
+
public get outboundVideoTracks(): ReadonlyMap<string, ObservedOutboundVideoTrack> {
|
|
171
181
|
return this._outboundVideoTracks;
|
|
172
182
|
}
|
|
173
183
|
|
|
@@ -218,13 +228,19 @@ export class ObservedPeerConnection extends EventEmitter {
|
|
|
218
228
|
|
|
219
229
|
Array.from(this._inboundAudioTracks.values()).forEach((track) => track.close());
|
|
220
230
|
Array.from(this._inboundVideoTracks.values()).forEach((track) => track.close());
|
|
221
|
-
Array.from(this._outboundAudioTracks.values()).forEach((track) =>
|
|
222
|
-
|
|
231
|
+
Array.from(this._outboundAudioTracks.values()).forEach((track) => {
|
|
232
|
+
this.client.call.sfuStreamIdToOutboundAudioTrack.delete(track.sfuStreamId ?? '');
|
|
233
|
+
track.close();
|
|
234
|
+
});
|
|
235
|
+
Array.from(this._outboundVideoTracks.values()).forEach((track) => {
|
|
236
|
+
this.client.call.sfuStreamIdToOutboundVideoTrack.delete(track.sfuStreamId ?? '');
|
|
237
|
+
track.close();
|
|
238
|
+
});
|
|
223
239
|
|
|
224
240
|
this.emit('close');
|
|
225
241
|
}
|
|
226
242
|
|
|
227
|
-
public getTrack(trackId: string):
|
|
243
|
+
public getTrack(trackId: string): ObservedInboundAudioTrack | ObservedInboundVideoTrack | ObservedOutboundAudioTrack | ObservedOutboundVideoTrack | undefined {
|
|
228
244
|
return this._inboundAudioTracks.get(trackId) ?? this._inboundVideoTracks.get(trackId) ?? this._outboundAudioTracks.get(trackId) ?? this._outboundVideoTracks.get(trackId);
|
|
229
245
|
}
|
|
230
246
|
|
|
@@ -251,8 +267,119 @@ export class ObservedPeerConnection extends EventEmitter {
|
|
|
251
267
|
this.ICE.resetMetrics();
|
|
252
268
|
}
|
|
253
269
|
|
|
270
|
+
public update(sample: PeerConnectionTransport, timestamp: number) {
|
|
271
|
+
if (this._closed) return;
|
|
272
|
+
if (sample.peerConnectionId !== this._model.peerConnectionId) throw new Error(`TransportId mismatch. PeerConnectionId: ${ this._model.peerConnectionId } TransportId: ${ sample.transportId}`);
|
|
273
|
+
|
|
274
|
+
this._sample = sample;
|
|
275
|
+
if (this._model.label !== sample.label) {
|
|
276
|
+
this._model.label = sample.label;
|
|
277
|
+
}
|
|
278
|
+
const now = Date.now();
|
|
279
|
+
const report: PeerConnectionTransportReport = {
|
|
280
|
+
serviceId: this.client.call.serviceId,
|
|
281
|
+
roomId: this.client.call.roomId,
|
|
282
|
+
callId: this.client.call.callId,
|
|
283
|
+
clientId: this.client.clientId,
|
|
284
|
+
userId: this.client.userId,
|
|
285
|
+
mediaUnitId: this.client.mediaUnitId,
|
|
286
|
+
...sample,
|
|
287
|
+
timestamp,
|
|
288
|
+
sampleSeq: -1, // deprecated
|
|
289
|
+
marker: this.marker,
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
this.reports.addPeerConnectionTransportReports(report);
|
|
293
|
+
this._elapsedTimeSinceLastUpdate = now - this._updated;
|
|
294
|
+
this.visited = true;
|
|
295
|
+
this._updated = now;
|
|
296
|
+
this._statsTimestamp = timestamp;
|
|
297
|
+
this.emit('update', {
|
|
298
|
+
elapsedTimeInMs: this._elapsedTimeSinceLastUpdate,
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
public createInboundAudioTrack(config: ObservedInboundAudioTrackModel): ObservedInboundAudioTrack {
|
|
303
|
+
if (this._closed) throw new Error(`PeerConnection ${this.peerConnectionId} is closed`);
|
|
304
|
+
|
|
305
|
+
const result = new ObservedInboundAudioTrack(config, this);
|
|
306
|
+
|
|
307
|
+
result.on('close', () => {
|
|
308
|
+
this._inboundAudioTracks.delete(result.trackId);
|
|
309
|
+
});
|
|
310
|
+
this._inboundAudioTracks.set(result.trackId, result);
|
|
311
|
+
|
|
312
|
+
this.emit('newinboudaudiotrack', result);
|
|
313
|
+
|
|
314
|
+
return result;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
public createInboundVideoTrack(config: ObservedInboundVideoTrackModel): ObservedInboundVideoTrack {
|
|
318
|
+
if (this._closed) throw new Error(`PeerConnection ${this.peerConnectionId} is closed`);
|
|
319
|
+
|
|
320
|
+
const result = new ObservedInboundVideoTrack(config, this);
|
|
321
|
+
|
|
322
|
+
result.on('close', () => {
|
|
323
|
+
this._inboundVideoTracks.delete(result.trackId);
|
|
324
|
+
});
|
|
325
|
+
this._inboundVideoTracks.set(result.trackId, result);
|
|
326
|
+
|
|
327
|
+
this.emit('newinboudvideotrack', result);
|
|
328
|
+
|
|
329
|
+
return result;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
public createOutboundAudioTrack(config: ObservedOutboundAudioTrackModel): ObservedOutboundAudioTrack {
|
|
333
|
+
if (this._closed) throw new Error(`PeerConnection ${this.peerConnectionId} is closed`);
|
|
334
|
+
|
|
335
|
+
const result = new ObservedOutboundAudioTrack(config, this);
|
|
336
|
+
|
|
337
|
+
result.on('close', () => {
|
|
338
|
+
this._outboundAudioTracks.delete(result.trackId);
|
|
339
|
+
});
|
|
340
|
+
this._outboundAudioTracks.set(result.trackId, result);
|
|
341
|
+
|
|
342
|
+
this.emit('newoutboundaudiotrack', result);
|
|
343
|
+
|
|
344
|
+
return result;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
public createOutboundVideoTrack(config: ObservedOutboundVideoTrackModel): ObservedOutboundVideoTrack {
|
|
348
|
+
if (this._closed) throw new Error(`PeerConnection ${this.peerConnectionId} is closed`);
|
|
349
|
+
|
|
350
|
+
const result = new ObservedOutboundVideoTrack(config, this);
|
|
351
|
+
|
|
352
|
+
result.on('close', () => {
|
|
353
|
+
this._outboundVideoTracks.delete(result.trackId);
|
|
354
|
+
});
|
|
355
|
+
this._outboundVideoTracks.set(result.trackId, result);
|
|
356
|
+
|
|
357
|
+
this.emit('newoutboundvideotrack', result);
|
|
358
|
+
|
|
359
|
+
return result;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
public createDataChannel(channelId: number) {
|
|
363
|
+
if (this._closed) throw new Error(`PeerConnection ${this.peerConnectionId} is closed`);
|
|
364
|
+
|
|
365
|
+
const result = new ObservedDataChannel({
|
|
366
|
+
channelId,
|
|
367
|
+
}, this);
|
|
368
|
+
|
|
369
|
+
result.on('close', () => {
|
|
370
|
+
this._dataChannels.delete(result.channelId);
|
|
371
|
+
});
|
|
372
|
+
this._dataChannels.set(result.channelId, result);
|
|
373
|
+
|
|
374
|
+
this.emit('newdatachannel', result);
|
|
375
|
+
|
|
376
|
+
return result;
|
|
377
|
+
}
|
|
378
|
+
|
|
254
379
|
public updateMetrics() {
|
|
255
380
|
let sumRttInMs = 0;
|
|
381
|
+
let sumJitter = 0;
|
|
382
|
+
const trackScores: CalculatedScore[] = [];
|
|
256
383
|
|
|
257
384
|
this._inboundAudioTracks.forEach((track) => {
|
|
258
385
|
track.updateMetrics();
|
|
@@ -266,7 +393,10 @@ export class ObservedPeerConnection extends EventEmitter {
|
|
|
266
393
|
|
|
267
394
|
this.receivingAudioBitrate += track.bitrate;
|
|
268
395
|
|
|
269
|
-
sumRttInMs
|
|
396
|
+
sumRttInMs += (track.rttInMs ?? 0);
|
|
397
|
+
sumJitter += (track.jitter ?? 0);
|
|
398
|
+
|
|
399
|
+
track.score && trackScores.push(track.score);
|
|
270
400
|
});
|
|
271
401
|
|
|
272
402
|
this._inboundVideoTracks.forEach((track) => {
|
|
@@ -281,7 +411,10 @@ export class ObservedPeerConnection extends EventEmitter {
|
|
|
281
411
|
|
|
282
412
|
this.receivingVideoBitrate += track.bitrate;
|
|
283
413
|
|
|
284
|
-
sumRttInMs
|
|
414
|
+
sumRttInMs += (track.rttInMs ?? 0);
|
|
415
|
+
sumJitter += (track.jitter ?? 0);
|
|
416
|
+
|
|
417
|
+
track.score && trackScores.push(track.score);
|
|
285
418
|
});
|
|
286
419
|
|
|
287
420
|
this._outboundAudioTracks.forEach((track) => {
|
|
@@ -295,7 +428,10 @@ export class ObservedPeerConnection extends EventEmitter {
|
|
|
295
428
|
|
|
296
429
|
this.sendingAudioBitrate += track.bitrate;
|
|
297
430
|
|
|
298
|
-
sumRttInMs
|
|
431
|
+
sumRttInMs += (track.rttInMs ?? 0);
|
|
432
|
+
sumJitter += (track.jitter ?? 0);
|
|
433
|
+
|
|
434
|
+
track.score && trackScores.push(track.score);
|
|
299
435
|
});
|
|
300
436
|
|
|
301
437
|
this._outboundVideoTracks.forEach((track) => {
|
|
@@ -309,7 +445,10 @@ export class ObservedPeerConnection extends EventEmitter {
|
|
|
309
445
|
|
|
310
446
|
this.sendingVideoBitrate += track.bitrate;
|
|
311
447
|
|
|
312
|
-
sumRttInMs
|
|
448
|
+
sumRttInMs += (track.rttInMs ?? 0);
|
|
449
|
+
sumJitter += (track.jitter ?? 0);
|
|
450
|
+
|
|
451
|
+
track.score && trackScores.push(track.score);
|
|
313
452
|
});
|
|
314
453
|
|
|
315
454
|
this._dataChannels.forEach((channel) => {
|
|
@@ -320,6 +459,8 @@ export class ObservedPeerConnection extends EventEmitter {
|
|
|
320
459
|
const iceRttInMs = this.ICE.stats?.currentRoundTripTime;
|
|
321
460
|
let nrOfBelongings = this._inboundAudioTracks.size + this._inboundVideoTracks.size + this._outboundAudioTracks.size + this._outboundVideoTracks.size;
|
|
322
461
|
|
|
462
|
+
this.avgJitter = 0 < nrOfBelongings ? sumJitter / nrOfBelongings : undefined;
|
|
463
|
+
|
|
323
464
|
if (iceRttInMs) {
|
|
324
465
|
sumRttInMs += iceRttInMs;
|
|
325
466
|
nrOfBelongings += 1;
|
|
@@ -347,125 +488,58 @@ export class ObservedPeerConnection extends EventEmitter {
|
|
|
347
488
|
this.sendingPacketsPerSecond = 0;
|
|
348
489
|
this.receivingPacketsPerSecond = 0;
|
|
349
490
|
}
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
public update(sample: PeerConnectionTransport, timestamp: number) {
|
|
353
|
-
if (this._closed) return;
|
|
354
|
-
if (sample.peerConnectionId !== this._model.peerConnectionId) throw new Error(`TransportId mismatch. PeerConnectionId: ${ this._model.peerConnectionId } TransportId: ${ sample.transportId}`);
|
|
355
|
-
|
|
356
|
-
this._sample = sample;
|
|
357
|
-
if (this._model.label !== sample.label) {
|
|
358
|
-
this._model.label = sample.label;
|
|
359
|
-
}
|
|
360
|
-
const now = Date.now();
|
|
361
|
-
const report: PeerConnectionTransportReport = {
|
|
362
|
-
serviceId: this.client.call.serviceId,
|
|
363
|
-
roomId: this.client.call.roomId,
|
|
364
|
-
callId: this.client.call.callId,
|
|
365
|
-
clientId: this.client.clientId,
|
|
366
|
-
userId: this.client.userId,
|
|
367
|
-
mediaUnitId: this.client.mediaUnitId,
|
|
368
|
-
...sample,
|
|
369
|
-
timestamp,
|
|
370
|
-
sampleSeq: -1, // deprecated
|
|
371
|
-
marker: this.marker,
|
|
372
|
-
};
|
|
373
|
-
|
|
374
|
-
this.reports.addPeerConnectionTransportReports(report);
|
|
375
|
-
this._elapsedTimeSinceLastUpdate = now - this._updated;
|
|
376
|
-
this.visited = true;
|
|
377
|
-
this._updated = now;
|
|
378
|
-
this.emit('update', {
|
|
379
|
-
elapsedTimeInMs: this._elapsedTimeSinceLastUpdate,
|
|
380
|
-
});
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
public createInboundAudioTrack(config: Omit<ObservedInboundTrackModel<'audio'>, 'kind'>): ObservedInboundTrack<'audio'> {
|
|
384
|
-
if (this._closed) throw new Error(`PeerConnection ${this.peerConnectionId} is closed`);
|
|
385
|
-
|
|
386
|
-
const result = new ObservedInboundTrack<'audio'>({
|
|
387
|
-
kind: 'audio',
|
|
388
|
-
...config,
|
|
389
|
-
}, this);
|
|
390
|
-
|
|
391
|
-
result.on('close', () => {
|
|
392
|
-
this._inboundAudioTracks.delete(result.trackId);
|
|
393
|
-
});
|
|
394
|
-
this._inboundAudioTracks.set(result.trackId, result);
|
|
395
|
-
|
|
396
|
-
this.emit('newinboudaudiotrack', result);
|
|
397
|
-
|
|
398
|
-
return result;
|
|
399
|
-
}
|
|
400
491
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
const result = new ObservedInboundTrack<'video'>({
|
|
405
|
-
kind: 'video',
|
|
406
|
-
...config,
|
|
407
|
-
}, this);
|
|
408
|
-
|
|
409
|
-
result.on('close', () => {
|
|
410
|
-
this._inboundVideoTracks.delete(result.trackId);
|
|
411
|
-
});
|
|
412
|
-
this._inboundVideoTracks.set(result.trackId, result);
|
|
413
|
-
|
|
414
|
-
this.emit('newinboudvideotrack', result);
|
|
415
|
-
|
|
416
|
-
return result;
|
|
492
|
+
// calculate quality score
|
|
493
|
+
this._updateQualityScore(trackScores);
|
|
417
494
|
}
|
|
418
495
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
...config,
|
|
425
|
-
}, this);
|
|
426
|
-
|
|
427
|
-
result.on('close', () => {
|
|
428
|
-
this._outboundAudioTracks.delete(result.trackId);
|
|
496
|
+
private _updateQualityScore(trackScores: CalculatedScore[]) {
|
|
497
|
+
const mosScore = calculateLatencyMOS({
|
|
498
|
+
avgJitter: this.avgJitter ?? 0,
|
|
499
|
+
rttInMs: this.avgRttInMs ?? 0,
|
|
500
|
+
packetsLoss: this.deltaInboundPacketsLost,
|
|
429
501
|
});
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
if (this._closed) throw new Error(`PeerConnection ${this.peerConnectionId} is closed`);
|
|
502
|
+
const score: CalculatedScore = {
|
|
503
|
+
remarks: [ {
|
|
504
|
+
severity: 'none',
|
|
505
|
+
text: `MOS score: ${mosScore}`,
|
|
506
|
+
} ],
|
|
507
|
+
score: mosScore / 10.0, // normalize between 0 and 2
|
|
508
|
+
timestamp: this._statsTimestamp ?? Date.now(),
|
|
509
|
+
};
|
|
439
510
|
|
|
440
|
-
|
|
441
|
-
kind: 'video',
|
|
442
|
-
...config,
|
|
443
|
-
}, this);
|
|
444
|
-
|
|
445
|
-
result.on('close', () => {
|
|
446
|
-
this._outboundVideoTracks.delete(result.trackId);
|
|
447
|
-
});
|
|
448
|
-
this._outboundVideoTracks.set(result.trackId, result);
|
|
511
|
+
let sumTrackScores = 0;
|
|
449
512
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
public createDataChannel(channelId: number) {
|
|
456
|
-
if (this._closed) throw new Error(`PeerConnection ${this.peerConnectionId} is closed`);
|
|
457
|
-
|
|
458
|
-
const result = new ObservedDataChannel({
|
|
459
|
-
channelId,
|
|
460
|
-
}, this);
|
|
513
|
+
for (const trackScore of trackScores) {
|
|
514
|
+
sumTrackScores += trackScore.score;
|
|
515
|
+
}
|
|
461
516
|
|
|
462
|
-
|
|
463
|
-
|
|
517
|
+
score.score += sumTrackScores / trackScores.length;
|
|
518
|
+
score.remarks.push({
|
|
519
|
+
severity: 'none',
|
|
520
|
+
text: `Track scores: ${sumTrackScores / trackScores.length}`,
|
|
464
521
|
});
|
|
465
|
-
this._dataChannels.set(result.channelId, result);
|
|
466
522
|
|
|
467
|
-
|
|
523
|
+
for (const pendingScore of this.ωpendingIssuesForScores) {
|
|
524
|
+
switch (pendingScore.severity) {
|
|
525
|
+
case 'critical':
|
|
526
|
+
score.score = 0.0;
|
|
527
|
+
break;
|
|
528
|
+
case 'major':
|
|
529
|
+
score.score *= 0.5;
|
|
530
|
+
break;
|
|
531
|
+
case 'minor':
|
|
532
|
+
score.score *= 0.8;
|
|
533
|
+
break;
|
|
534
|
+
}
|
|
535
|
+
score.remarks.push({
|
|
536
|
+
severity: pendingScore.severity,
|
|
537
|
+
text: pendingScore.description ?? 'Issue occurred',
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
this.ωpendingIssuesForScores = [];
|
|
541
|
+
this.score = score;
|
|
468
542
|
|
|
469
|
-
|
|
543
|
+
this.emit('score', score);
|
|
470
544
|
}
|
|
471
545
|
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { ObservedInboundAudioTrack } from './ObservedInboundAudioTrack';
|
|
2
|
+
import { ObservedInboundVideoTrack } from './ObservedInboundVideoTrack';
|
|
3
|
+
import { ObservedOutboundAudioTrack } from './ObservedOutboundAudioTrack';
|
|
4
|
+
import { ObservedOutboundVideoTrack } from './ObservedOutboundVideoTrack';
|
|
5
|
+
|
|
6
|
+
export class RemoteTrackAssigner {
|
|
7
|
+
public readonly sfuStreamIdToOutboundVideoTracks = new Map<string, ObservedOutboundVideoTrack>();
|
|
8
|
+
public readonly sfuStreamIdToOutboundAudioTracks = new Map<string, ObservedOutboundAudioTrack>();
|
|
9
|
+
public readonly pendingInboundVideoTracks = new Map<string, ObservedInboundVideoTrack[]>();
|
|
10
|
+
public readonly pendingInboundAudioTracks = new Map<string, ObservedInboundAudioTrack[]>();
|
|
11
|
+
|
|
12
|
+
public addOutboundAudioTrack(track: ObservedOutboundAudioTrack) {
|
|
13
|
+
const untilSfuStreamIdListener = () => {
|
|
14
|
+
if (!track.sfuStreamId) return;
|
|
15
|
+
|
|
16
|
+
this.sfuStreamIdToOutboundAudioTracks.set(track.sfuStreamId, track);
|
|
17
|
+
track.off('update', untilSfuStreamIdListener);
|
|
18
|
+
|
|
19
|
+
// we need to assign to the pending inbound tracks
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
track.on('update', untilSfuStreamIdListener);
|
|
23
|
+
untilSfuStreamIdListener();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public addOutboundVideoTrack(track: ObservedOutboundVideoTrack) {
|
|
27
|
+
const untilSfuStreamIdListener = () => {
|
|
28
|
+
if (!track.sfuStreamId) return;
|
|
29
|
+
|
|
30
|
+
this.sfuStreamIdToOutboundVideoTracks.set(track.sfuStreamId, track);
|
|
31
|
+
track.off('update', untilSfuStreamIdListener);
|
|
32
|
+
|
|
33
|
+
// we need to assign to the pending inbound tracks
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
track.on('update', untilSfuStreamIdListener);
|
|
37
|
+
untilSfuStreamIdListener();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
public addInboundAudioTrack(track: ObservedInboundAudioTrack) {
|
|
41
|
+
const untilSfuStreamIdListener = () => {
|
|
42
|
+
if (!track.sfuStreamId) return;
|
|
43
|
+
const outboundTrack = this.sfuStreamIdToOutboundAudioTracks.get(track.sfuStreamId);
|
|
44
|
+
|
|
45
|
+
if (!outboundTrack) {
|
|
46
|
+
this.pendingInboundAudioTracks.set(
|
|
47
|
+
track.sfuStreamId,
|
|
48
|
+
(this.pendingInboundAudioTracks.get(track.sfuStreamId) || []).concat(track)
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (outboundTrack) {
|
|
55
|
+
track.remoteOutboundTrack = outboundTrack;
|
|
56
|
+
} else {
|
|
57
|
+
this.pendingInboundAudioTracks.set(
|
|
58
|
+
track.sfuStreamId,
|
|
59
|
+
(this.pendingInboundAudioTracks.get(track.sfuStreamId) || []).concat(track)
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
track.on('update', untilSfuStreamIdListener);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
}
|