@observertc/observer-js 0.30.0-e12fcb4.0 → 0.30.2-beta
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 +1 -1
- package/lib/ObservedCall.d.ts.map +1 -1
- package/lib/ObservedCall.js +0 -7
- package/lib/ObservedClient.d.ts +36 -6
- package/lib/ObservedClient.d.ts.map +1 -1
- package/lib/ObservedClient.js +129 -71
- package/lib/ObservedICE.d.ts +47 -0
- package/lib/ObservedICE.d.ts.map +1 -0
- package/lib/ObservedICE.js +105 -0
- package/lib/ObservedInboundTrack.d.ts +16 -0
- package/lib/ObservedInboundTrack.d.ts.map +1 -1
- package/lib/ObservedInboundTrack.js +39 -2
- package/lib/ObservedPeerConnection.d.ts +39 -1
- package/lib/ObservedPeerConnection.d.ts.map +1 -1
- package/lib/ObservedPeerConnection.js +61 -0
- package/lib/Observer.d.ts +1 -4
- package/lib/Observer.d.ts.map +1 -1
- package/lib/Observer.js +1 -0
- package/lib/common/CallEventType.d.ts +22 -0
- package/lib/common/CallEventType.d.ts.map +1 -0
- package/lib/common/CallEventType.js +26 -0
- package/lib/common/callEventReports.d.ts +0 -10
- package/lib/common/callEventReports.d.ts.map +1 -1
- package/lib/common/callEventReports.js +10 -21
- package/lib/models/Models.d.ts +3 -70
- package/lib/models/Models.d.ts.map +1 -1
- package/lib/models/Models.js +2 -72
- package/lib/models/samples_pb.js +1 -1
- package/package.json +1 -1
- package/src/ObservedCall.ts +3 -29
- package/src/ObservedClient.ts +168 -84
- package/src/ObservedICE.ts +176 -0
- package/src/ObservedInboundTrack.ts +50 -1
- package/src/ObservedPeerConnection.ts +79 -2
- package/src/Observer.ts +4 -5
- package/src/common/CallEventType.ts +23 -0
- package/src/common/callEventReports.ts +1 -11
- package/src/models/Models.ts +4 -113
- package/src/models/samples_pb.ts +1 -1
package/src/ObservedClient.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ClientSample
|
|
1
|
+
import { ClientSample } from '@observertc/sample-schemas-js';
|
|
2
2
|
import { ObservedCall } from './ObservedCall';
|
|
3
3
|
import * as Models from './models/Models';
|
|
4
4
|
import { StorageProvider } from './storages/StorageProvider';
|
|
@@ -9,8 +9,9 @@ import { createLogger } from './common/logger';
|
|
|
9
9
|
import { CallMetaType, createCallMetaReport } from './common/callMetaReports';
|
|
10
10
|
// eslint-disable-next-line camelcase
|
|
11
11
|
import { Samples_ClientSample_Browser, Samples_ClientSample_Engine, Samples_ClientSample_OperationSystem, Samples_ClientSample_Platform } from './models/samples_pb';
|
|
12
|
-
import { IceCandidatePairReport } from '@observertc/report-schemas-js';
|
|
13
12
|
import { isValidUuid } from './common/utils';
|
|
13
|
+
import { createClientJoinedEventReport, createClientLeftEventReport } from './common/callEventReports';
|
|
14
|
+
import { CallEventType } from './common/CallEventType';
|
|
14
15
|
|
|
15
16
|
const logger = createLogger('ObservedClient');
|
|
16
17
|
|
|
@@ -21,13 +22,17 @@ export type ObservedClientConfig<AppData extends Record<string, unknown> = Recor
|
|
|
21
22
|
appData: AppData;
|
|
22
23
|
overflowingProcessingThreshold?: number;
|
|
23
24
|
joined?: number,
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* before client-monitor-js 3.4.0 the client joined event was generated by the observer, this flag is to keep the compatibility with the old behavior
|
|
28
|
+
*/
|
|
29
|
+
addJoinAndDetachClientEvent?: boolean;
|
|
24
30
|
};
|
|
25
31
|
|
|
26
32
|
export type ObservedClientEvents = {
|
|
27
33
|
update: [],
|
|
28
34
|
close: [],
|
|
29
35
|
'processing-sample-overflow': [],
|
|
30
|
-
'candidate-pair-change': [],
|
|
31
36
|
newpeerconnection: [ObservedPeerConnection],
|
|
32
37
|
};
|
|
33
38
|
|
|
@@ -65,6 +70,21 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
|
|
|
65
70
|
|
|
66
71
|
result._overflowingProcessingThreshold = config.overflowingProcessingThreshold ?? 0;
|
|
67
72
|
|
|
73
|
+
if (config.addJoinAndDetachClientEvent) {
|
|
74
|
+
call.reports.addCallEventReport(createClientJoinedEventReport(
|
|
75
|
+
call.serviceId,
|
|
76
|
+
result.mediaUnitId,
|
|
77
|
+
call.roomId,
|
|
78
|
+
call.callId,
|
|
79
|
+
result.clientId,
|
|
80
|
+
result.joined,
|
|
81
|
+
result.userId,
|
|
82
|
+
result.marker,
|
|
83
|
+
));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
result.once('close', () => result._addClientLeftReport());
|
|
87
|
+
|
|
68
88
|
return result;
|
|
69
89
|
}
|
|
70
90
|
|
|
@@ -76,9 +96,39 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
|
|
|
76
96
|
private _updated = Date.now();
|
|
77
97
|
private _acceptedSample = 0;
|
|
78
98
|
private _processingSample = 0;
|
|
79
|
-
private
|
|
80
|
-
private
|
|
81
|
-
|
|
99
|
+
private _timeZoneOffsetInHours?: number;
|
|
100
|
+
private _leaveReported = false;
|
|
101
|
+
|
|
102
|
+
public totalInboundPacketsLost = 0;
|
|
103
|
+
public totalInboundPacketsReceived = 0;
|
|
104
|
+
public totalOutboundPacketsLost = 0;
|
|
105
|
+
public totalOutboundPacketsReceived = 0;
|
|
106
|
+
public totalOutboundPacketsSent = 0;
|
|
107
|
+
public totalDataChannelBytesSent = 0;
|
|
108
|
+
public totalDataChannelBytesReceived = 0;
|
|
109
|
+
public totalSentAudioBytes = 0;
|
|
110
|
+
public totalSentVideoBytes = 0;
|
|
111
|
+
public totalReceivedAudioBytes = 0;
|
|
112
|
+
public totalReceivedVideoBytes = 0;
|
|
113
|
+
|
|
114
|
+
public deltaInboundPacketsLost?: number;
|
|
115
|
+
public deltaInboundPacketsReceived?: number;
|
|
116
|
+
public deltaOutboundPacketsLost?: number;
|
|
117
|
+
public deltaOutboundPacketsReceived?: number;
|
|
118
|
+
public deltaOutboundPacketsSent?: number;
|
|
119
|
+
public deltaDataChannelBytesSent?: number;
|
|
120
|
+
public deltaDataChannelBytesReceived?: number;
|
|
121
|
+
public deltaReceivedAudioBytes?: number;
|
|
122
|
+
public deltaReceivedVideoBytes?: number;
|
|
123
|
+
public deltaSentAudioBytes?: number;
|
|
124
|
+
public deltaSentVideoBytes?: number;
|
|
125
|
+
|
|
126
|
+
public avgRttInS?: number;
|
|
127
|
+
public sendingAudioBitrate?: number;
|
|
128
|
+
public sendingVideoBitrate?: number;
|
|
129
|
+
public receivingAudioBitrate?: number;
|
|
130
|
+
public receivingVideoBitrate?: number;
|
|
131
|
+
|
|
82
132
|
public readonly mediaDevices: string[] = [];
|
|
83
133
|
public readonly codecs: string[] = [];
|
|
84
134
|
public readonly userMediaErrors: string[] = [];
|
|
@@ -130,12 +180,8 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
|
|
|
130
180
|
return this._model.userId;
|
|
131
181
|
}
|
|
132
182
|
|
|
133
|
-
public get
|
|
134
|
-
return this.
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
public get iceRemoteCandidate() {
|
|
138
|
-
return this._iceRemoteCandidate;
|
|
183
|
+
public get timeZoneOffsetInHours() {
|
|
184
|
+
return this._timeZoneOffsetInHours;
|
|
139
185
|
}
|
|
140
186
|
|
|
141
187
|
public get marker() {
|
|
@@ -209,6 +255,11 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
|
|
|
209
255
|
executeSave = true;
|
|
210
256
|
}
|
|
211
257
|
|
|
258
|
+
if (this._timeZoneOffsetInHours !== sample.timeZoneOffsetInHours) {
|
|
259
|
+
this._timeZoneOffsetInHours = sample.timeZoneOffsetInHours;
|
|
260
|
+
executeSave = true;
|
|
261
|
+
}
|
|
262
|
+
|
|
212
263
|
if (sample.os && (
|
|
213
264
|
this._model.operationSystem?.name !== sample.os.name ||
|
|
214
265
|
this._model.operationSystem?.version !== sample.os.version ||
|
|
@@ -348,6 +399,23 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
|
|
|
348
399
|
}
|
|
349
400
|
|
|
350
401
|
for (const { timestamp = Date.now(), ...callEvent } of sample.customCallEvents ?? []) {
|
|
402
|
+
const peerConnection = this._peerConnections.get(callEvent.peerConnectionId ?? '');
|
|
403
|
+
|
|
404
|
+
switch (callEvent.name) {
|
|
405
|
+
case CallEventType.CLIENT_LEFT:
|
|
406
|
+
this._leaveReported = true;
|
|
407
|
+
break;
|
|
408
|
+
case CallEventType.ICE_CONNECTION_STATE_CHANGED:
|
|
409
|
+
peerConnection && callEvent.value && peerConnection.emit('iceconnectionstatechange', callEvent.value);
|
|
410
|
+
break;
|
|
411
|
+
case CallEventType.ICE_GATHERING_STATE_CHANGED:
|
|
412
|
+
peerConnection && callEvent.value && peerConnection.emit('icegatheringstatechange', callEvent.value);
|
|
413
|
+
break;
|
|
414
|
+
case CallEventType.PEER_CONNECTION_STATE_CHANGED:
|
|
415
|
+
peerConnection && callEvent.value && peerConnection.emit('connectionstatechange', callEvent.value);
|
|
416
|
+
break;
|
|
417
|
+
}
|
|
418
|
+
|
|
351
419
|
this.reports.addCallEventReport({
|
|
352
420
|
serviceId: this.serviceId,
|
|
353
421
|
mediaUnitId: this.mediaUnitId,
|
|
@@ -446,75 +514,6 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
|
|
|
446
514
|
|
|
447
515
|
this.reports.addCallMetaReport(callMetaReport);
|
|
448
516
|
}
|
|
449
|
-
let selectedLocalCandidateId: string | undefined;
|
|
450
|
-
let selectedRemoteCandidateId: string | undefined;
|
|
451
|
-
|
|
452
|
-
for (const candidatePair of sample.iceCandidatePairs ?? []) {
|
|
453
|
-
if (candidatePair.nominated) {
|
|
454
|
-
if (!selectedLocalCandidateId) selectedLocalCandidateId = candidatePair.localCandidateId;
|
|
455
|
-
if (!selectedRemoteCandidateId) selectedRemoteCandidateId = candidatePair.remoteCandidateId;
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
const report: IceCandidatePairReport = {
|
|
459
|
-
serviceId: this.serviceId,
|
|
460
|
-
mediaUnitId: this.mediaUnitId,
|
|
461
|
-
roomId: this.roomId,
|
|
462
|
-
callId: this.callId,
|
|
463
|
-
clientId: this.clientId,
|
|
464
|
-
userId: this.userId,
|
|
465
|
-
timestamp: sample.timestamp,
|
|
466
|
-
...candidatePair,
|
|
467
|
-
sampleSeq: -1, // deprecated
|
|
468
|
-
};
|
|
469
|
-
|
|
470
|
-
this.reports.addIceCandidatePairReport(report);
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
let changeIceCandidatePair = false;
|
|
474
|
-
|
|
475
|
-
for (const iceLocalCandidate of sample.iceLocalCandidates ?? []) {
|
|
476
|
-
if (
|
|
477
|
-
iceLocalCandidate.id && selectedLocalCandidateId &&
|
|
478
|
-
iceLocalCandidate.id === selectedLocalCandidateId &&
|
|
479
|
-
this._iceLocalCandidate?.id !== iceLocalCandidate.id
|
|
480
|
-
) {
|
|
481
|
-
this._iceLocalCandidate = iceLocalCandidate;
|
|
482
|
-
changeIceCandidatePair = true;
|
|
483
|
-
}
|
|
484
|
-
const callMetaReport = createCallMetaReport(
|
|
485
|
-
this.serviceId,
|
|
486
|
-
this.mediaUnitId,
|
|
487
|
-
this.roomId,
|
|
488
|
-
this.callId,
|
|
489
|
-
this.clientId, {
|
|
490
|
-
type: CallMetaType.ICE_LOCAL_CANDIDATE,
|
|
491
|
-
payload: iceLocalCandidate,
|
|
492
|
-
}, this.userId);
|
|
493
|
-
|
|
494
|
-
this.reports.addCallMetaReport(callMetaReport);
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
for (const iceRemoteCandidate of sample.iceRemoteCandidates ?? []) {
|
|
498
|
-
if (
|
|
499
|
-
iceRemoteCandidate.id && selectedRemoteCandidateId &&
|
|
500
|
-
iceRemoteCandidate.id === selectedRemoteCandidateId &&
|
|
501
|
-
this._iceRemoteCandidate?.id !== iceRemoteCandidate.id
|
|
502
|
-
) {
|
|
503
|
-
this._iceRemoteCandidate = iceRemoteCandidate;
|
|
504
|
-
changeIceCandidatePair = true;
|
|
505
|
-
}
|
|
506
|
-
const callMetaReport = createCallMetaReport(
|
|
507
|
-
this.serviceId,
|
|
508
|
-
this.mediaUnitId,
|
|
509
|
-
this.roomId,
|
|
510
|
-
this.callId,
|
|
511
|
-
this.clientId, {
|
|
512
|
-
type: CallMetaType.ICE_REMOTE_CANDIDATE,
|
|
513
|
-
payload: iceRemoteCandidate,
|
|
514
|
-
}, this.userId);
|
|
515
|
-
|
|
516
|
-
this.reports.addCallMetaReport(callMetaReport);
|
|
517
|
-
}
|
|
518
517
|
|
|
519
518
|
for (const transport of sample.pcTransports ?? []) {
|
|
520
519
|
try {
|
|
@@ -640,9 +639,78 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
|
|
|
640
639
|
logger.error(`Error creating outbound video track: ${(err as Error)?.message}`);
|
|
641
640
|
}
|
|
642
641
|
}
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
642
|
+
|
|
643
|
+
for (const iceLocalCandidate of sample.iceLocalCandidates ?? []) {
|
|
644
|
+
if (!iceLocalCandidate.peerConnectionId) {
|
|
645
|
+
logger.warn(`Local ice candidate without peerConnectionId: ${JSON.stringify(iceLocalCandidate)}`);
|
|
646
|
+
continue;
|
|
647
|
+
}
|
|
648
|
+
const peerConnection = this._peerConnections.get(iceLocalCandidate.peerConnectionId);
|
|
649
|
+
|
|
650
|
+
if (!peerConnection) {
|
|
651
|
+
logger.debug(`Peer connection ${iceLocalCandidate.peerConnectionId} not found for ice local candidate ${iceLocalCandidate.id}`);
|
|
652
|
+
continue;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
peerConnection.ICE.addLocalCandidate(iceLocalCandidate);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
for (const iceRemoteCandidate of sample.iceRemoteCandidates ?? []) {
|
|
659
|
+
if (!iceRemoteCandidate.peerConnectionId) {
|
|
660
|
+
logger.warn(`Remote ice candidate without peerConnectionId: ${JSON.stringify(iceRemoteCandidate)}`);
|
|
661
|
+
continue;
|
|
662
|
+
}
|
|
663
|
+
const peerConnection = this._peerConnections.get(iceRemoteCandidate.peerConnectionId);
|
|
664
|
+
|
|
665
|
+
if (!peerConnection) {
|
|
666
|
+
logger.debug(`Peer connection ${iceRemoteCandidate.peerConnectionId} not found for ice remote candidate ${iceRemoteCandidate.id}`);
|
|
667
|
+
continue;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
peerConnection.ICE.addRemoteCandidate(iceRemoteCandidate);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
for (const candidatePair of sample.iceCandidatePairs ?? []) {
|
|
674
|
+
const peerConnection = this._peerConnections.get(candidatePair.peerConnectionId);
|
|
675
|
+
|
|
676
|
+
if (!peerConnection) {
|
|
677
|
+
logger.debug(`Peer connection ${candidatePair.peerConnectionId} not found for ice candidate pair ${candidatePair.localCandidateId}, ${candidatePair.remoteCandidateId}`);
|
|
678
|
+
|
|
679
|
+
continue;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
peerConnection.ICE.update(candidatePair, sample.timestamp);
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// close entries that are idle for too long
|
|
686
|
+
if (this.call.observer.config.maxEntryIdleTimeInMs && this._updated < sample.timestamp) {
|
|
687
|
+
|
|
688
|
+
for (const peerConnection of this._peerConnections.values()) {
|
|
689
|
+
if (this.call.observer.config.maxEntryIdleTimeInMs < sample.timestamp - peerConnection.updated) {
|
|
690
|
+
logger.debug(`Closing peer connection ${peerConnection.peerConnectionId} due to inactivity`);
|
|
691
|
+
peerConnection.close();
|
|
692
|
+
|
|
693
|
+
continue;
|
|
694
|
+
}
|
|
695
|
+
for (const track of peerConnection.inboundAudioTracks.values()) {
|
|
696
|
+
if (this.call.observer.config.maxEntryIdleTimeInMs < sample.timestamp - track.updated) {
|
|
697
|
+
logger.debug(`Closing inbound audio track ${track.trackId} due to inactivity`);
|
|
698
|
+
track.close();
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
for (const track of peerConnection.inboundVideoTracks.values()) {
|
|
702
|
+
if (this.call.observer.config.maxEntryIdleTimeInMs < sample.timestamp - track.updated) {
|
|
703
|
+
logger.debug(`Closing inbound video track ${track.trackId} due to inactivity`);
|
|
704
|
+
track.close();
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
for (const track of peerConnection.outboundAudioTracks.values()) {
|
|
708
|
+
if (this.call.observer.config.maxEntryIdleTimeInMs < sample.timestamp - track.updated) {
|
|
709
|
+
logger.debug(`Closing outbound audio track ${track.trackId} due to inactivity`);
|
|
710
|
+
track.close();
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
}
|
|
646
714
|
}
|
|
647
715
|
|
|
648
716
|
if (executeSave) await this._save();
|
|
@@ -678,4 +746,20 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
|
|
|
678
746
|
|
|
679
747
|
return this._storageProvider.clientStorage.set(this.clientId, this._model);
|
|
680
748
|
}
|
|
749
|
+
|
|
750
|
+
private _addClientLeftReport() {
|
|
751
|
+
if (this._leaveReported) return;
|
|
752
|
+
this._leaveReported = true;
|
|
753
|
+
|
|
754
|
+
this.reports.addCallEventReport(createClientLeftEventReport(
|
|
755
|
+
this.serviceId,
|
|
756
|
+
this.mediaUnitId,
|
|
757
|
+
this.roomId,
|
|
758
|
+
this.callId,
|
|
759
|
+
this.clientId,
|
|
760
|
+
this.joined,
|
|
761
|
+
this.userId,
|
|
762
|
+
this.marker,
|
|
763
|
+
));
|
|
764
|
+
}
|
|
681
765
|
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { IceCandidatePair, IceLocalCandidate, IceRemoteCandidate } from '@observertc/sample-schemas-js';
|
|
2
|
+
import { EventEmitter } from 'events';
|
|
3
|
+
import { ObservedPeerConnection } from './ObservedPeerConnection';
|
|
4
|
+
import { IceCandidatePairReport } from '@observertc/report-schemas-js';
|
|
5
|
+
import { CallMetaType, createCallMetaReport } from './common/callMetaReports';
|
|
6
|
+
|
|
7
|
+
export type ObservedICEEvents = {
|
|
8
|
+
update: [],
|
|
9
|
+
'new-local-candidate': [IceLocalCandidate],
|
|
10
|
+
'new-remote-candidate': [IceRemoteCandidate],
|
|
11
|
+
'new-selected-candidate-pair': [{
|
|
12
|
+
localCandidate: IceLocalCandidate,
|
|
13
|
+
remoteCandidate: IceRemoteCandidate,
|
|
14
|
+
}],
|
|
15
|
+
close: [],
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export declare interface ObservedICE {
|
|
19
|
+
on<U extends keyof ObservedICEEvents>(event: U, listener: (...args: ObservedICEEvents[U]) => void): this;
|
|
20
|
+
off<U extends keyof ObservedICEEvents>(event: U, listener: (...args: ObservedICEEvents[U]) => void): this;
|
|
21
|
+
once<U extends keyof ObservedICEEvents>(event: U, listener: (...args: ObservedICEEvents[U]) => void): this;
|
|
22
|
+
emit<U extends keyof ObservedICEEvents>(event: U, ...args: ObservedICEEvents[U]): boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class ObservedICE extends EventEmitter {
|
|
26
|
+
public static create(peerConnection: ObservedPeerConnection,) {
|
|
27
|
+
return new ObservedICE(peerConnection);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
private readonly _localCandidates = new Map<string, IceLocalCandidate>();
|
|
31
|
+
private readonly _remoteCandidates = new Map<string, IceLocalCandidate>();
|
|
32
|
+
|
|
33
|
+
private _selectedLocalCandidateId?: string;
|
|
34
|
+
private _selectedRemoteCandidateId?: string;
|
|
35
|
+
private _updated = Date.now();
|
|
36
|
+
private _stats?: IceCandidatePair;
|
|
37
|
+
private _closed = false;
|
|
38
|
+
|
|
39
|
+
private constructor(
|
|
40
|
+
public readonly peerConnection: ObservedPeerConnection,
|
|
41
|
+
) {
|
|
42
|
+
super();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
public get reports() {
|
|
46
|
+
return this.peerConnection.reports;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public get localCandidates(): ReadonlyMap<string, IceLocalCandidate> {
|
|
50
|
+
return this._localCandidates;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
public get remoteCandidates(): ReadonlyMap<string, IceRemoteCandidate> {
|
|
54
|
+
return this._remoteCandidates;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
public get selectedLocalCandidate() {
|
|
58
|
+
return this._localCandidates.get(this._selectedLocalCandidateId ?? '');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
public get selectedRemoteCandidate() {
|
|
62
|
+
return this._remoteCandidates.get(this._selectedRemoteCandidateId ?? '');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
public get stats() {
|
|
66
|
+
return this._stats;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
public get updated() {
|
|
70
|
+
return this._updated;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
public get closed() {
|
|
74
|
+
return this._closed;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
public close() {
|
|
78
|
+
if (this._closed) return;
|
|
79
|
+
this._closed = true;
|
|
80
|
+
this.emit('close');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
public addLocalCandidate(candidate: IceLocalCandidate) {
|
|
84
|
+
if (!candidate.id) return;
|
|
85
|
+
|
|
86
|
+
const newCandidate = !this._localCandidates.has(candidate.id);
|
|
87
|
+
|
|
88
|
+
this._localCandidates.set(candidate.id, candidate);
|
|
89
|
+
|
|
90
|
+
if (newCandidate) {
|
|
91
|
+
const callMetaReport = createCallMetaReport(
|
|
92
|
+
this.peerConnection.client.serviceId,
|
|
93
|
+
this.peerConnection.client.mediaUnitId,
|
|
94
|
+
this.peerConnection.client.roomId,
|
|
95
|
+
this.peerConnection.client.callId,
|
|
96
|
+
this.peerConnection.client.clientId, {
|
|
97
|
+
type: CallMetaType.ICE_LOCAL_CANDIDATE,
|
|
98
|
+
payload: candidate,
|
|
99
|
+
}, this.peerConnection.client.userId);
|
|
100
|
+
|
|
101
|
+
this.reports.addCallMetaReport(callMetaReport);
|
|
102
|
+
|
|
103
|
+
this.emit('new-local-candidate', candidate);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
public addRemoteCandidate(candidate: IceRemoteCandidate) {
|
|
108
|
+
if (!candidate.id) return;
|
|
109
|
+
|
|
110
|
+
const newCandidate = !this._remoteCandidates.has(candidate.id);
|
|
111
|
+
|
|
112
|
+
this._remoteCandidates.set(candidate.id, candidate);
|
|
113
|
+
|
|
114
|
+
if (newCandidate) {
|
|
115
|
+
const callMetaReport = createCallMetaReport(
|
|
116
|
+
this.peerConnection.client.serviceId,
|
|
117
|
+
this.peerConnection.client.mediaUnitId,
|
|
118
|
+
this.peerConnection.client.roomId,
|
|
119
|
+
this.peerConnection.client.callId,
|
|
120
|
+
this.peerConnection.client.clientId, {
|
|
121
|
+
type: CallMetaType.ICE_REMOTE_CANDIDATE,
|
|
122
|
+
payload: candidate,
|
|
123
|
+
}, this.peerConnection.client.userId);
|
|
124
|
+
|
|
125
|
+
this.reports.addCallMetaReport(callMetaReport);
|
|
126
|
+
|
|
127
|
+
this.emit('new-remote-candidate', candidate);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
public update(candidatePair: IceCandidatePair, timestamp: number) {
|
|
132
|
+
|
|
133
|
+
const report: IceCandidatePairReport = {
|
|
134
|
+
serviceId: this.peerConnection.client.serviceId,
|
|
135
|
+
mediaUnitId: this.peerConnection.client.mediaUnitId,
|
|
136
|
+
roomId: this.peerConnection.client.roomId,
|
|
137
|
+
callId: this.peerConnection.client.callId,
|
|
138
|
+
clientId: this.peerConnection.client.clientId,
|
|
139
|
+
userId: this.peerConnection.client.userId,
|
|
140
|
+
timestamp,
|
|
141
|
+
...candidatePair,
|
|
142
|
+
sampleSeq: -1, // deprecated
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
this.reports.addIceCandidatePairReport(report);
|
|
146
|
+
|
|
147
|
+
if (!candidatePair.nominated) return;
|
|
148
|
+
|
|
149
|
+
this._stats = candidatePair;
|
|
150
|
+
|
|
151
|
+
if (
|
|
152
|
+
candidatePair.localCandidateId &&
|
|
153
|
+
candidatePair.localCandidateId !== this._selectedLocalCandidateId &&
|
|
154
|
+
this._localCandidates.has(candidatePair.localCandidateId) &&
|
|
155
|
+
candidatePair.remoteCandidateId &&
|
|
156
|
+
candidatePair.remoteCandidateId !== this._selectedRemoteCandidateId &&
|
|
157
|
+
this._remoteCandidates.has(candidatePair.remoteCandidateId)
|
|
158
|
+
) {
|
|
159
|
+
this._selectedLocalCandidateId = candidatePair.localCandidateId;
|
|
160
|
+
this._selectedRemoteCandidateId = candidatePair.remoteCandidateId;
|
|
161
|
+
|
|
162
|
+
const localCandidate = this._localCandidates.get(candidatePair.localCandidateId);
|
|
163
|
+
const remoteCandidate = this._remoteCandidates.get(candidatePair.remoteCandidateId);
|
|
164
|
+
|
|
165
|
+
if (localCandidate && remoteCandidate) {
|
|
166
|
+
this.emit('new-selected-candidate-pair', {
|
|
167
|
+
localCandidate,
|
|
168
|
+
remoteCandidate,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
this._updated = timestamp;
|
|
174
|
+
this.emit('update');
|
|
175
|
+
}
|
|
176
|
+
}
|
|
@@ -28,6 +28,14 @@ export type ObservedInboundTrackStats<K extends MediaKind> = ObservedInboundTrac
|
|
|
28
28
|
bitrate: number;
|
|
29
29
|
fractionLost: number;
|
|
30
30
|
rttInMs?: number;
|
|
31
|
+
lostPackets?: number;
|
|
32
|
+
receivedPackets?: number;
|
|
33
|
+
receivedFrames?: number;
|
|
34
|
+
decodedFrames?: number;
|
|
35
|
+
droppedFrames?: number;
|
|
36
|
+
receivedSamples?: number;
|
|
37
|
+
silentConcealedSamples?: number;
|
|
38
|
+
fractionLoss?: number;
|
|
31
39
|
};
|
|
32
40
|
|
|
33
41
|
// {
|
|
@@ -76,6 +84,14 @@ export class ObservedInboundTrack<Kind extends MediaKind> extends EventEmitter {
|
|
|
76
84
|
private _remoteOutboundTrack?: ObservedOutboundTrack<Kind>;
|
|
77
85
|
public bitrate = -1;
|
|
78
86
|
public rttInMs = -1;
|
|
87
|
+
public lostPackets = 0;
|
|
88
|
+
public receivedPackets = 0;
|
|
89
|
+
public receivedFrames = 0;
|
|
90
|
+
public decodedFrames = 0;
|
|
91
|
+
public droppedFrames = 0;
|
|
92
|
+
public receivedSamples = 0;
|
|
93
|
+
public silentConcealedSamples = 0;
|
|
94
|
+
public fractionLoss = 0;
|
|
79
95
|
|
|
80
96
|
private constructor(
|
|
81
97
|
private readonly _model: Models.InboundTrack,
|
|
@@ -211,19 +227,52 @@ export class ObservedInboundTrack<Kind extends MediaKind> extends EventEmitter {
|
|
|
211
227
|
const lostPackets = (sample.packetsLost ?? 0) - (lastStat?.packetsLost ?? 0);
|
|
212
228
|
const receivedPackets = (sample.packetsReceived ?? 0) - (lastStat?.packetsReceived ?? 0);
|
|
213
229
|
const fractionLost = 0 < receivedPackets && 0 < lostPackets ? (lostPackets / (receivedPackets + lostPackets)) : 0;
|
|
230
|
+
let decodedFrames: number | undefined;
|
|
231
|
+
let droppedFrames: number | undefined;
|
|
232
|
+
let receivedFrames: number | undefined;
|
|
233
|
+
let silentConcealedSamples: number | undefined;
|
|
234
|
+
|
|
235
|
+
if (this.kind === 'video') {
|
|
236
|
+
const videoSample = sample as InboundVideoTrack;
|
|
237
|
+
const lastVideoStat = lastStat as InboundVideoTrack | undefined;
|
|
238
|
+
|
|
239
|
+
decodedFrames = (videoSample.framesDecoded ?? 0) - (lastVideoStat?.framesDecoded ?? 0);
|
|
240
|
+
droppedFrames = (videoSample.framesDropped ?? 0) - (lastVideoStat?.framesDropped ?? 0);
|
|
241
|
+
receivedFrames = (videoSample.framesReceived ?? 0) - (lastVideoStat?.framesReceived ?? 0);
|
|
242
|
+
} else if (this.kind === 'audio') {
|
|
243
|
+
const audioSample = sample as InboundAudioTrack;
|
|
244
|
+
const lastAudioStat = lastStat as InboundAudioTrack | undefined;
|
|
245
|
+
|
|
246
|
+
silentConcealedSamples = (audioSample.silentConcealedSamples ?? 0) - (lastAudioStat?.silentConcealedSamples ?? 0);
|
|
247
|
+
}
|
|
248
|
+
|
|
214
249
|
const stats: ObservedInboundTrackStats<Kind> = {
|
|
215
250
|
...sample,
|
|
216
251
|
fractionLost,
|
|
217
252
|
rttInMs,
|
|
218
253
|
bitrate,
|
|
219
254
|
ssrc: sample.ssrc,
|
|
255
|
+
lostPackets,
|
|
256
|
+
receivedPackets,
|
|
257
|
+
receivedFrames,
|
|
258
|
+
decodedFrames,
|
|
259
|
+
droppedFrames,
|
|
260
|
+
silentConcealedSamples,
|
|
220
261
|
};
|
|
221
262
|
|
|
222
263
|
this._stats.set(sample.ssrc, stats);
|
|
223
264
|
|
|
224
265
|
this.bitrate = [ ...this._stats.values() ].reduce((acc, stat) => acc + stat.bitrate, 0);
|
|
225
266
|
this.rttInMs = [ ...this._stats.values() ].reduce((acc, stat) => acc + (stat.rttInMs ?? 0), 0) / (this._stats.size || 1);
|
|
226
|
-
|
|
267
|
+
this.lostPackets = [ ...this._stats.values() ].reduce((acc, stat) => acc + (stat.lostPackets ?? 0), 0);
|
|
268
|
+
this.receivedPackets = [ ...this._stats.values() ].reduce((acc, stat) => acc + (stat.receivedPackets ?? 0), 0);
|
|
269
|
+
this.receivedFrames = [ ...this._stats.values() ].reduce((acc, stat) => acc + (stat.receivedFrames ?? 0), 0);
|
|
270
|
+
this.decodedFrames = [ ...this._stats.values() ].reduce((acc, stat) => acc + (stat.decodedFrames ?? 0), 0);
|
|
271
|
+
this.droppedFrames = [ ...this._stats.values() ].reduce((acc, stat) => acc + (stat.droppedFrames ?? 0), 0);
|
|
272
|
+
this.receivedSamples = [ ...this._stats.values() ].reduce((acc, stat) => acc + (stat.receivedSamples ?? 0), 0);
|
|
273
|
+
this.silentConcealedSamples = [ ...this._stats.values() ].reduce((acc, stat) => acc + (stat.silentConcealedSamples ?? 0), 0);
|
|
274
|
+
this.fractionLoss = 0 < this.receivedPackets && 0 < this.lostPackets ? (this.lostPackets / (this.receivedPackets + this.lostPackets)) : 0;
|
|
275
|
+
|
|
227
276
|
if (executeSave) await this._save();
|
|
228
277
|
|
|
229
278
|
this._updated = timestamp;
|