@observertc/observer-js 0.30.1-beta → 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.
@@ -1,10 +1,10 @@
1
1
  "use strict";
2
- // @generated by protoc-gen-es v1.1.1 with parameter "target=ts"
2
+ // @generated by protoc-gen-es v1.7.2 with parameter "target=ts"
3
3
  // @generated from file models.proto (package org.observertc.observer.models, syntax proto2)
4
4
  /* eslint-disable */
5
5
  // @ts-nocheck
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.SfuSctpChannel = exports.SfuOutboundRtpPad = exports.SfuInboundRtpPad = exports.SfuTransport = exports.Sfu = exports.OutboundTrack = exports.InboundTrack = exports.InboundTrackStats = exports.InboundTrackVideoStats = exports.PeerConnection = exports.Client = exports.ClientCoordinate = exports.Call = void 0;
7
+ exports.SfuSctpChannel = exports.SfuOutboundRtpPad = exports.SfuInboundRtpPad = exports.SfuTransport = exports.Sfu = exports.OutboundTrack = exports.InboundTrack = exports.InboundTrackStats = exports.InboundTrackVideoStats = exports.PeerConnection = exports.Client = exports.Call = void 0;
8
8
  const protobuf_1 = require("@bufbuild/protobuf");
9
9
  const samples_pb_1 = require("./samples_pb");
10
10
  /**
@@ -42,34 +42,6 @@ Call.fields = protobuf_1.proto2.util.newFieldList(() => [
42
42
  { no: 4, name: "started", kind: "scalar", T: 4 /* ScalarType.UINT64 */ },
43
43
  { no: 5, name: "clientIds", kind: "scalar", T: 9 /* ScalarType.STRING */, repeated: true },
44
44
  ]);
45
- /**
46
- * @generated from message org.observertc.observer.models.ClientCoordinate
47
- */
48
- class ClientCoordinate extends protobuf_1.Message {
49
- constructor(data) {
50
- super();
51
- protobuf_1.proto2.util.initPartial(data, this);
52
- }
53
- static fromBinary(bytes, options) {
54
- return new ClientCoordinate().fromBinary(bytes, options);
55
- }
56
- static fromJson(jsonValue, options) {
57
- return new ClientCoordinate().fromJson(jsonValue, options);
58
- }
59
- static fromJsonString(jsonString, options) {
60
- return new ClientCoordinate().fromJsonString(jsonString, options);
61
- }
62
- static equals(a, b) {
63
- return protobuf_1.proto2.util.equals(ClientCoordinate, a, b);
64
- }
65
- }
66
- exports.ClientCoordinate = ClientCoordinate;
67
- ClientCoordinate.runtime = protobuf_1.proto2;
68
- ClientCoordinate.typeName = "org.observertc.observer.models.ClientCoordinate";
69
- ClientCoordinate.fields = protobuf_1.proto2.util.newFieldList(() => [
70
- { no: 1, name: "latitude", kind: "scalar", T: 2 /* ScalarType.FLOAT */ },
71
- { no: 2, name: "longitude", kind: "scalar", T: 2 /* ScalarType.FLOAT */ },
72
- ]);
73
45
  /**
74
46
  * @generated from message org.observertc.observer.models.Client
75
47
  */
@@ -114,7 +86,6 @@ Client.fields = protobuf_1.proto2.util.newFieldList(() => [
114
86
  { no: 13, name: "operationSystem", kind: "message", T: samples_pb_1.Samples_ClientSample_OperationSystem, opt: true },
115
87
  { no: 14, name: "platform", kind: "message", T: samples_pb_1.Samples_ClientSample_Platform, opt: true },
116
88
  { no: 15, name: "engine", kind: "message", T: samples_pb_1.Samples_ClientSample_Engine, opt: true },
117
- { no: 16, name: "coordinates", kind: "message", T: ClientCoordinate, opt: true },
118
89
  ]);
119
90
  /**
120
91
  * @generated from message org.observertc.observer.models.PeerConnection
@@ -130,22 +101,6 @@ class PeerConnection extends protobuf_1.Message {
130
101
  * @generated from field: repeated string outboundTrackIds = 12;
131
102
  */
132
103
  this.outboundTrackIds = [];
133
- /**
134
- * @generated from field: repeated org.observertc.schemas.protobuf.Samples.ClientSample.IceLocalCandidate icelocalCandidates = 13;
135
- */
136
- this.icelocalCandidates = [];
137
- /**
138
- * @generated from field: repeated org.observertc.schemas.protobuf.Samples.ClientSample.IceRemoteCandidate iceRemoteCandidates = 14;
139
- */
140
- this.iceRemoteCandidates = [];
141
- /**
142
- * @generated from field: repeated org.observertc.schemas.protobuf.Samples.ClientSample.IceCandidatePair iceCandidatePairs = 15;
143
- */
144
- this.iceCandidatePairs = [];
145
- /**
146
- * @generated from field: repeated org.observertc.schemas.protobuf.Samples.ClientSample.PeerConnectionTransport transports = 16;
147
- */
148
- this.transports = [];
149
104
  protobuf_1.proto2.util.initPartial(data, this);
150
105
  }
151
106
  static fromBinary(bytes, options) {
@@ -177,10 +132,6 @@ PeerConnection.fields = protobuf_1.proto2.util.newFieldList(() => [
177
132
  { no: 10, name: "label", kind: "scalar", T: 9 /* ScalarType.STRING */, opt: true },
178
133
  { no: 11, name: "inboundTrackIds", kind: "scalar", T: 9 /* ScalarType.STRING */, repeated: true },
179
134
  { no: 12, name: "outboundTrackIds", kind: "scalar", T: 9 /* ScalarType.STRING */, repeated: true },
180
- { no: 13, name: "icelocalCandidates", kind: "message", T: samples_pb_1.Samples_ClientSample_IceLocalCandidate, repeated: true },
181
- { no: 14, name: "iceRemoteCandidates", kind: "message", T: samples_pb_1.Samples_ClientSample_IceRemoteCandidate, repeated: true },
182
- { no: 15, name: "iceCandidatePairs", kind: "message", T: samples_pb_1.Samples_ClientSample_IceCandidatePair, repeated: true },
183
- { no: 16, name: "transports", kind: "message", T: samples_pb_1.Samples_ClientSample_PeerConnectionTransport, repeated: true },
184
135
  { no: 17, name: "touched", kind: "scalar", T: 4 /* ScalarType.UINT64 */, opt: true },
185
136
  ]);
186
137
  /**
@@ -210,7 +161,6 @@ InboundTrackVideoStats.typeName = "org.observertc.observer.models.InboundTrackVi
210
161
  InboundTrackVideoStats.fields = protobuf_1.proto2.util.newFieldList(() => [
211
162
  { no: 1, name: "ssrc", kind: "scalar", T: 5 /* ScalarType.INT32 */ },
212
163
  { no: 2, name: "fps", kind: "scalar", T: 2 /* ScalarType.FLOAT */, opt: true },
213
- { no: 4, name: "sample", kind: "message", T: samples_pb_1.Samples_ClientSample_InboundVideoTrack, opt: true },
214
164
  ]);
215
165
  /**
216
166
  * @generated from message org.observertc.observer.models.InboundTrackStats
@@ -251,14 +201,6 @@ class InboundTrack extends protobuf_1.Message {
251
201
  * @generated from field: repeated int64 ssrc = 14;
252
202
  */
253
203
  this.ssrc = [];
254
- /**
255
- * @generated from field: repeated org.observertc.schemas.protobuf.Samples.ClientSample.InboundAudioTrack audioStats = 15;
256
- */
257
- this.audioStats = [];
258
- /**
259
- * @generated from field: repeated org.observertc.schemas.protobuf.Samples.ClientSample.InboundVideoTrack videoStats = 16;
260
- */
261
- this.videoStats = [];
262
204
  protobuf_1.proto2.util.initPartial(data, this);
263
205
  }
264
206
  static fromBinary(bytes, options) {
@@ -292,8 +234,6 @@ InboundTrack.fields = protobuf_1.proto2.util.newFieldList(() => [
292
234
  { no: 12, name: "sfuSinkId", kind: "scalar", T: 9 /* ScalarType.STRING */, opt: true },
293
235
  { no: 13, name: "sfuStreamId", kind: "scalar", T: 9 /* ScalarType.STRING */, opt: true },
294
236
  { no: 14, name: "ssrc", kind: "scalar", T: 3 /* ScalarType.INT64 */, repeated: true },
295
- { no: 15, name: "audioStats", kind: "message", T: samples_pb_1.Samples_ClientSample_InboundAudioTrack, repeated: true },
296
- { no: 16, name: "videoStats", kind: "message", T: samples_pb_1.Samples_ClientSample_InboundVideoTrack, repeated: true },
297
237
  { no: 17, name: "touched", kind: "scalar", T: 4 /* ScalarType.UINT64 */, opt: true },
298
238
  ]);
299
239
  /**
@@ -306,14 +246,6 @@ class OutboundTrack extends protobuf_1.Message {
306
246
  * @generated from field: repeated int64 ssrc = 13;
307
247
  */
308
248
  this.ssrc = [];
309
- /**
310
- * @generated from field: repeated org.observertc.schemas.protobuf.Samples.ClientSample.OutboundAudioTrack audioStats = 14;
311
- */
312
- this.audioStats = [];
313
- /**
314
- * @generated from field: repeated org.observertc.schemas.protobuf.Samples.ClientSample.OutboundVideoTrack videoStats = 15;
315
- */
316
- this.videoStats = [];
317
249
  protobuf_1.proto2.util.initPartial(data, this);
318
250
  }
319
251
  static fromBinary(bytes, options) {
@@ -346,8 +278,6 @@ OutboundTrack.fields = protobuf_1.proto2.util.newFieldList(() => [
346
278
  { no: 11, name: "marker", kind: "scalar", T: 9 /* ScalarType.STRING */, opt: true },
347
279
  { no: 12, name: "sfuStreamId", kind: "scalar", T: 9 /* ScalarType.STRING */, opt: true },
348
280
  { no: 13, name: "ssrc", kind: "scalar", T: 3 /* ScalarType.INT64 */, repeated: true },
349
- { no: 14, name: "audioStats", kind: "message", T: samples_pb_1.Samples_ClientSample_OutboundAudioTrack, repeated: true },
350
- { no: 15, name: "videoStats", kind: "message", T: samples_pb_1.Samples_ClientSample_OutboundVideoTrack, repeated: true },
351
281
  { no: 16, name: "touched", kind: "scalar", T: 4 /* ScalarType.UINT64 */, opt: true },
352
282
  ]);
353
283
  /**
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
- // @generated by protoc-gen-es v1.1.1 with parameter "target=ts"
2
+ // @generated by protoc-gen-es v1.7.2 with parameter "target=ts"
3
3
  // @generated from file samples.proto (package org.observertc.schemas.protobuf, syntax proto2)
4
4
  /* eslint-disable */
5
5
  // @ts-nocheck
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@observertc/observer-js",
3
- "version": "0.30.1-beta",
3
+ "version": "0.30.2-beta",
4
4
  "description": "Server Side NodeJS Library for processing ObserveRTC Samples",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -1,4 +1,4 @@
1
- import { ClientSample, IceLocalCandidate } from '@observertc/sample-schemas-js';
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,9 +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';
14
13
  import { createClientJoinedEventReport, createClientLeftEventReport } from './common/callEventReports';
14
+ import { CallEventType } from './common/CallEventType';
15
15
 
16
16
  const logger = createLogger('ObservedClient');
17
17
 
@@ -33,7 +33,6 @@ export type ObservedClientEvents = {
33
33
  update: [],
34
34
  close: [],
35
35
  'processing-sample-overflow': [],
36
- 'candidate-pair-change': [],
37
36
  newpeerconnection: [ObservedPeerConnection],
38
37
  };
39
38
 
@@ -97,11 +96,39 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
97
96
  private _updated = Date.now();
98
97
  private _acceptedSample = 0;
99
98
  private _processingSample = 0;
100
- private _selectedLocalIceCandidate?: IceLocalCandidate;
101
- private _selectedRemoteIceCandidate?: IceLocalCandidate;
102
99
  private _timeZoneOffsetInHours?: number;
103
100
  private _leaveReported = false;
104
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
+
105
132
  public readonly mediaDevices: string[] = [];
106
133
  public readonly codecs: string[] = [];
107
134
  public readonly userMediaErrors: string[] = [];
@@ -153,14 +180,6 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
153
180
  return this._model.userId;
154
181
  }
155
182
 
156
- public get selectedLocalIceCandidate() {
157
- return this._selectedLocalIceCandidate;
158
- }
159
-
160
- public get selectedRemoteIceCandidate() {
161
- return this._selectedRemoteIceCandidate;
162
- }
163
-
164
183
  public get timeZoneOffsetInHours() {
165
184
  return this._timeZoneOffsetInHours;
166
185
  }
@@ -380,8 +399,23 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
380
399
  }
381
400
 
382
401
  for (const { timestamp = Date.now(), ...callEvent } of sample.customCallEvents ?? []) {
383
- if (callEvent.name === 'CLIENT_LEFT') this._leaveReported = true;
402
+ const peerConnection = this._peerConnections.get(callEvent.peerConnectionId ?? '');
384
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
+
385
419
  this.reports.addCallEventReport({
386
420
  serviceId: this.serviceId,
387
421
  mediaUnitId: this.mediaUnitId,
@@ -480,76 +514,6 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
480
514
 
481
515
  this.reports.addCallMetaReport(callMetaReport);
482
516
  }
483
- let selectedLocalCandidateId: string | undefined;
484
- let selectedRemoteCandidateId: string | undefined;
485
-
486
- for (const candidatePair of sample.iceCandidatePairs ?? []) {
487
- if (candidatePair.nominated) {
488
- if (!selectedLocalCandidateId) selectedLocalCandidateId = candidatePair.localCandidateId;
489
- if (!selectedRemoteCandidateId) selectedRemoteCandidateId = candidatePair.remoteCandidateId;
490
- }
491
-
492
- const report: IceCandidatePairReport = {
493
- serviceId: this.serviceId,
494
- mediaUnitId: this.mediaUnitId,
495
- roomId: this.roomId,
496
- callId: this.callId,
497
- clientId: this.clientId,
498
- userId: this.userId,
499
- timestamp: sample.timestamp,
500
- ...candidatePair,
501
- sampleSeq: -1, // deprecated
502
- };
503
-
504
- this.reports.addIceCandidatePairReport(report);
505
-
506
- }
507
-
508
- let changeIceCandidatePair = false;
509
-
510
- for (const iceLocalCandidate of sample.iceLocalCandidates ?? []) {
511
- if (
512
- iceLocalCandidate.id && selectedLocalCandidateId &&
513
- iceLocalCandidate.id === selectedLocalCandidateId &&
514
- this._selectedLocalIceCandidate?.id !== iceLocalCandidate.id
515
- ) {
516
- this._selectedLocalIceCandidate = iceLocalCandidate;
517
- changeIceCandidatePair = true;
518
- }
519
- const callMetaReport = createCallMetaReport(
520
- this.serviceId,
521
- this.mediaUnitId,
522
- this.roomId,
523
- this.callId,
524
- this.clientId, {
525
- type: CallMetaType.ICE_LOCAL_CANDIDATE,
526
- payload: iceLocalCandidate,
527
- }, this.userId);
528
-
529
- this.reports.addCallMetaReport(callMetaReport);
530
- }
531
-
532
- for (const iceRemoteCandidate of sample.iceRemoteCandidates ?? []) {
533
- if (
534
- iceRemoteCandidate.id && selectedRemoteCandidateId &&
535
- iceRemoteCandidate.id === selectedRemoteCandidateId &&
536
- this._selectedRemoteIceCandidate?.id !== iceRemoteCandidate.id
537
- ) {
538
- this._selectedRemoteIceCandidate = iceRemoteCandidate;
539
- changeIceCandidatePair = true;
540
- }
541
- const callMetaReport = createCallMetaReport(
542
- this.serviceId,
543
- this.mediaUnitId,
544
- this.roomId,
545
- this.callId,
546
- this.clientId, {
547
- type: CallMetaType.ICE_REMOTE_CANDIDATE,
548
- payload: iceRemoteCandidate,
549
- }, this.userId);
550
-
551
- this.reports.addCallMetaReport(callMetaReport);
552
- }
553
517
 
554
518
  for (const transport of sample.pcTransports ?? []) {
555
519
  try {
@@ -676,6 +640,48 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
676
640
  }
677
641
  }
678
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
+
679
685
  // close entries that are idle for too long
680
686
  if (this.call.observer.config.maxEntryIdleTimeInMs && this._updated < sample.timestamp) {
681
687
 
@@ -707,10 +713,6 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
707
713
  }
708
714
  }
709
715
 
710
- if (changeIceCandidatePair) {
711
- this.emit('candidate-pair-change');
712
- }
713
-
714
716
  if (executeSave) await this._save();
715
717
 
716
718
  this._updated = sample.timestamp;
@@ -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;