@observertc/observer-js 0.30.1-beta → 0.30.3-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/ObservedClient.d.ts +15 -6
- package/lib/ObservedClient.d.ts.map +1 -1
- package/lib/ObservedClient.js +146 -73
- 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 +29 -1
- package/lib/ObservedInboundTrack.d.ts.map +1 -1
- package/lib/ObservedInboundTrack.js +78 -8
- package/lib/ObservedOutboundTrack.d.ts +20 -3
- package/lib/ObservedOutboundTrack.d.ts.map +1 -1
- package/lib/ObservedOutboundTrack.js +53 -9
- package/lib/ObservedPeerConnection.d.ts +47 -1
- package/lib/ObservedPeerConnection.d.ts.map +1 -1
- package/lib/ObservedPeerConnection.js +155 -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/ObservedClient.ts +146 -89
- package/src/ObservedICE.ts +176 -0
- package/src/ObservedInboundTrack.ts +97 -3
- package/src/ObservedOutboundTrack.ts +70 -9
- package/src/ObservedPeerConnection.ts +205 -1
- 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/lib/common/ReportsCollector.d.ts +0 -23
- package/lib/common/ReportsCollector.d.ts.map +0 -1
- package/lib/common/ReportsCollector.js +0 -26
- package/src/common/ReportsCollector.ts +0 -71
|
@@ -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
|
// {
|
|
@@ -67,6 +75,8 @@ export class ObservedInboundTrack<Kind extends MediaKind> extends EventEmitter {
|
|
|
67
75
|
|
|
68
76
|
return new ObservedInboundTrack<K>(model, peerConnection, storageProvider);
|
|
69
77
|
}
|
|
78
|
+
|
|
79
|
+
public created = Date.now();
|
|
70
80
|
|
|
71
81
|
private readonly _stats = new Map<number, ObservedInboundTrackStats<Kind>>();
|
|
72
82
|
private readonly _execute = createSingleExecutor();
|
|
@@ -74,8 +84,27 @@ export class ObservedInboundTrack<Kind extends MediaKind> extends EventEmitter {
|
|
|
74
84
|
private _closed = false;
|
|
75
85
|
private _updated = Date.now();
|
|
76
86
|
private _remoteOutboundTrack?: ObservedOutboundTrack<Kind>;
|
|
77
|
-
public bitrate =
|
|
78
|
-
public rttInMs
|
|
87
|
+
public bitrate = 0;
|
|
88
|
+
public rttInMs?: number;
|
|
89
|
+
public fractionLoss = 0;
|
|
90
|
+
|
|
91
|
+
public totalLostPackets = 0;
|
|
92
|
+
public totalReceivedPackets = 0;
|
|
93
|
+
public totalBytesReceived = 0;
|
|
94
|
+
public totalReceivedFrames = 0;
|
|
95
|
+
public totalDecodedFrames = 0;
|
|
96
|
+
public totalDroppedFrames = 0;
|
|
97
|
+
public totalReceivedSamples = 0;
|
|
98
|
+
public totalSilentConcealedSamples = 0;
|
|
99
|
+
|
|
100
|
+
public deltaLostPackets = 0;
|
|
101
|
+
public deltaReceivedPackets = 0;
|
|
102
|
+
public deltaBytesReceived = 0;
|
|
103
|
+
public deltaReceivedFrames = 0;
|
|
104
|
+
public deltaDecodedFrames = 0;
|
|
105
|
+
public deltaDroppedFrames = 0;
|
|
106
|
+
public deltaReceivedSamples = 0;
|
|
107
|
+
public deltaSilentConcealedSamples = 0;
|
|
79
108
|
|
|
80
109
|
private constructor(
|
|
81
110
|
private readonly _model: Models.InboundTrack,
|
|
@@ -145,6 +174,10 @@ export class ObservedInboundTrack<Kind extends MediaKind> extends EventEmitter {
|
|
|
145
174
|
return this.peerConnection.reports;
|
|
146
175
|
}
|
|
147
176
|
|
|
177
|
+
public get uptimeInMs() {
|
|
178
|
+
return this._updated - this.created;
|
|
179
|
+
}
|
|
180
|
+
|
|
148
181
|
public set remoteOutboundTrack(track: ObservedOutboundTrack<Kind> | undefined) {
|
|
149
182
|
if (this._closed) return;
|
|
150
183
|
if (this._remoteOutboundTrack) return;
|
|
@@ -174,6 +207,19 @@ export class ObservedInboundTrack<Kind extends MediaKind> extends EventEmitter {
|
|
|
174
207
|
.finally(() => this.emit('close'));
|
|
175
208
|
}
|
|
176
209
|
|
|
210
|
+
public resetMetrics() {
|
|
211
|
+
this.bitrate = 0;
|
|
212
|
+
this.rttInMs = undefined;
|
|
213
|
+
this.deltaBytesReceived = 0;
|
|
214
|
+
this.deltaLostPackets = 0;
|
|
215
|
+
this.deltaReceivedFrames = 0;
|
|
216
|
+
this.deltaDecodedFrames = 0;
|
|
217
|
+
this.deltaDroppedFrames = 0;
|
|
218
|
+
this.deltaReceivedSamples = 0;
|
|
219
|
+
this.deltaSilentConcealedSamples = 0;
|
|
220
|
+
this.fractionLoss = 0;
|
|
221
|
+
}
|
|
222
|
+
|
|
177
223
|
public async update(sample: ObservedInboundTrackStatsUpdate<Kind>, timestamp: number) {
|
|
178
224
|
if (this._closed) return;
|
|
179
225
|
let executeSave = false;
|
|
@@ -189,6 +235,11 @@ export class ObservedInboundTrack<Kind extends MediaKind> extends EventEmitter {
|
|
|
189
235
|
...sample,
|
|
190
236
|
timestamp,
|
|
191
237
|
sampleSeq: -1,
|
|
238
|
+
|
|
239
|
+
remoteClientId: sample.remoteClientId ?? this.remoteOutboundTrack?.peerConnection.client.clientId,
|
|
240
|
+
remoteUserId: this.remoteOutboundTrack?.peerConnection.client.userId,
|
|
241
|
+
remoteTrackId: this.remoteOutboundTrack?.trackId,
|
|
242
|
+
remotePeerConnectionId: this.remoteOutboundTrack?.peerConnection.peerConnectionId,
|
|
192
243
|
};
|
|
193
244
|
|
|
194
245
|
if (this.kind === 'audio') this.reports.addInboundAudioTrackReport(report);
|
|
@@ -211,19 +262,62 @@ export class ObservedInboundTrack<Kind extends MediaKind> extends EventEmitter {
|
|
|
211
262
|
const lostPackets = (sample.packetsLost ?? 0) - (lastStat?.packetsLost ?? 0);
|
|
212
263
|
const receivedPackets = (sample.packetsReceived ?? 0) - (lastStat?.packetsReceived ?? 0);
|
|
213
264
|
const fractionLost = 0 < receivedPackets && 0 < lostPackets ? (lostPackets / (receivedPackets + lostPackets)) : 0;
|
|
265
|
+
let decodedFrames: number | undefined;
|
|
266
|
+
let droppedFrames: number | undefined;
|
|
267
|
+
let receivedFrames: number | undefined;
|
|
268
|
+
let silentConcealedSamples: number | undefined;
|
|
269
|
+
|
|
270
|
+
if (this.kind === 'video') {
|
|
271
|
+
const videoSample = sample as InboundVideoTrack;
|
|
272
|
+
const lastVideoStat = lastStat as InboundVideoTrack | undefined;
|
|
273
|
+
|
|
274
|
+
decodedFrames = (videoSample.framesDecoded ?? 0) - (lastVideoStat?.framesDecoded ?? 0);
|
|
275
|
+
droppedFrames = (videoSample.framesDropped ?? 0) - (lastVideoStat?.framesDropped ?? 0);
|
|
276
|
+
receivedFrames = (videoSample.framesReceived ?? 0) - (lastVideoStat?.framesReceived ?? 0);
|
|
277
|
+
} else if (this.kind === 'audio') {
|
|
278
|
+
const audioSample = sample as InboundAudioTrack;
|
|
279
|
+
const lastAudioStat = lastStat as InboundAudioTrack | undefined;
|
|
280
|
+
|
|
281
|
+
silentConcealedSamples = (audioSample.silentConcealedSamples ?? 0) - (lastAudioStat?.silentConcealedSamples ?? 0);
|
|
282
|
+
}
|
|
283
|
+
|
|
214
284
|
const stats: ObservedInboundTrackStats<Kind> = {
|
|
215
285
|
...sample,
|
|
216
286
|
fractionLost,
|
|
217
287
|
rttInMs,
|
|
218
288
|
bitrate,
|
|
219
289
|
ssrc: sample.ssrc,
|
|
290
|
+
lostPackets,
|
|
291
|
+
receivedPackets,
|
|
292
|
+
receivedFrames,
|
|
293
|
+
decodedFrames,
|
|
294
|
+
droppedFrames,
|
|
295
|
+
silentConcealedSamples,
|
|
220
296
|
};
|
|
221
297
|
|
|
222
298
|
this._stats.set(sample.ssrc, stats);
|
|
223
299
|
|
|
224
300
|
this.bitrate = [ ...this._stats.values() ].reduce((acc, stat) => acc + stat.bitrate, 0);
|
|
225
301
|
this.rttInMs = [ ...this._stats.values() ].reduce((acc, stat) => acc + (stat.rttInMs ?? 0), 0) / (this._stats.size || 1);
|
|
226
|
-
|
|
302
|
+
this.totalLostPackets = [ ...this._stats.values() ].reduce((acc, stat) => acc + (stat.lostPackets ?? 0), 0);
|
|
303
|
+
this.totalReceivedPackets = [ ...this._stats.values() ].reduce((acc, stat) => acc + (stat.receivedPackets ?? 0), 0);
|
|
304
|
+
this.totalBytesReceived = [ ...this._stats.values() ].reduce((acc, stat) => acc + (stat.bytesReceived ?? 0), 0);
|
|
305
|
+
this.totalReceivedFrames = [ ...this._stats.values() ].reduce((acc, stat) => acc + (stat.receivedFrames ?? 0), 0);
|
|
306
|
+
this.totalDecodedFrames = [ ...this._stats.values() ].reduce((acc, stat) => acc + (stat.decodedFrames ?? 0), 0);
|
|
307
|
+
this.totalDroppedFrames = [ ...this._stats.values() ].reduce((acc, stat) => acc + (stat.droppedFrames ?? 0), 0);
|
|
308
|
+
this.totalReceivedSamples = [ ...this._stats.values() ].reduce((acc, stat) => acc + (stat.receivedSamples ?? 0), 0);
|
|
309
|
+
this.totalSilentConcealedSamples = [ ...this._stats.values() ].reduce((acc, stat) => acc + (stat.silentConcealedSamples ?? 0), 0);
|
|
310
|
+
|
|
311
|
+
this.deltaLostPackets = [ ...this._stats.values() ].reduce((acc, stat) => acc + (stat.lostPackets ?? 0), 0);
|
|
312
|
+
this.deltaReceivedPackets = [ ...this._stats.values() ].reduce((acc, stat) => acc + (stat.receivedPackets ?? 0), 0);
|
|
313
|
+
this.deltaBytesReceived = [ ...this._stats.values() ].reduce((acc, stat) => acc + (stat.bytesReceived ?? 0), 0);
|
|
314
|
+
this.deltaReceivedFrames = [ ...this._stats.values() ].reduce((acc, stat) => acc + (stat.receivedFrames ?? 0), 0);
|
|
315
|
+
this.deltaDecodedFrames = [ ...this._stats.values() ].reduce((acc, stat) => acc + (stat.decodedFrames ?? 0), 0);
|
|
316
|
+
this.deltaDroppedFrames = [ ...this._stats.values() ].reduce((acc, stat) => acc + (stat.droppedFrames ?? 0), 0);
|
|
317
|
+
this.deltaReceivedSamples = [ ...this._stats.values() ].reduce((acc, stat) => acc + (stat.receivedSamples ?? 0), 0);
|
|
318
|
+
this.deltaSilentConcealedSamples = [ ...this._stats.values() ].reduce((acc, stat) => acc + (stat.silentConcealedSamples ?? 0), 0);
|
|
319
|
+
this.fractionLoss = 0 < this.deltaReceivedPackets && 0 < this.deltaLostPackets ? (this.deltaLostPackets / (this.deltaReceivedPackets + this.deltaLostPackets)) : 0;
|
|
320
|
+
|
|
227
321
|
if (executeSave) await this._save();
|
|
228
322
|
|
|
229
323
|
this._updated = timestamp;
|
|
@@ -25,6 +25,12 @@ export type ObservedOutboundTrackStats<K extends MediaKind> = ObservedOutboundTr
|
|
|
25
25
|
ssrc: number;
|
|
26
26
|
bitrate: number;
|
|
27
27
|
rttInMs?: number;
|
|
28
|
+
|
|
29
|
+
deltaLostPackets: number;
|
|
30
|
+
deltaSentPackets: number;
|
|
31
|
+
deltaSentBytes: number;
|
|
32
|
+
deltaSentFrames?: number;
|
|
33
|
+
deltaEncodedFrames?: number;
|
|
28
34
|
};
|
|
29
35
|
|
|
30
36
|
// export type ObservedOutboundTrackStatsUpdate<K extends MediaKind> = {
|
|
@@ -64,9 +70,22 @@ export class ObservedOutboundTrack<Kind extends MediaKind> extends EventEmitter
|
|
|
64
70
|
return new ObservedOutboundTrack<K>(model, peerConnection, storageProvider);
|
|
65
71
|
}
|
|
66
72
|
|
|
67
|
-
public bitrate
|
|
73
|
+
public bitrate = 0;
|
|
68
74
|
public rttInMs?: number;
|
|
69
75
|
|
|
76
|
+
public totalLostPackets = 0;
|
|
77
|
+
public totalSentPackets = 0;
|
|
78
|
+
public totalSentBytes = 0;
|
|
79
|
+
public totalSentFrames = 0;
|
|
80
|
+
|
|
81
|
+
public deltaLostPackets = 0;
|
|
82
|
+
public deltaSentPackets = 0;
|
|
83
|
+
public deltaSentBytes = 0;
|
|
84
|
+
public deltaSentFrames = 0;
|
|
85
|
+
public deltaEncodedFrames = 0;
|
|
86
|
+
|
|
87
|
+
public created = Date.now();
|
|
88
|
+
|
|
70
89
|
private readonly _stats = new Map<number, ObservedOutboundTrackStats<Kind>>();
|
|
71
90
|
private readonly _execute = createSingleExecutor();
|
|
72
91
|
|
|
@@ -96,10 +115,6 @@ export class ObservedOutboundTrack<Kind extends MediaKind> extends EventEmitter
|
|
|
96
115
|
return this._model.sfuStreamId;
|
|
97
116
|
}
|
|
98
117
|
|
|
99
|
-
public get closed() {
|
|
100
|
-
return this._closed;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
118
|
public get remoteInboundTracks(): ReadonlyMap<string, ObservedInboundTrack<Kind>> {
|
|
104
119
|
return this._remoteInboundTracks;
|
|
105
120
|
}
|
|
@@ -111,7 +126,19 @@ export class ObservedOutboundTrack<Kind extends MediaKind> extends EventEmitter
|
|
|
111
126
|
public get updated() {
|
|
112
127
|
return this._updated;
|
|
113
128
|
}
|
|
129
|
+
|
|
130
|
+
public get stats(): ReadonlyMap<number, ObservedOutboundTrackStats<Kind>> {
|
|
131
|
+
return this._stats;
|
|
132
|
+
}
|
|
114
133
|
|
|
134
|
+
public get uptimeInMs() {
|
|
135
|
+
return this._updated - this.created;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
public get closed() {
|
|
139
|
+
return this._closed;
|
|
140
|
+
}
|
|
141
|
+
|
|
115
142
|
public close() {
|
|
116
143
|
if (this._closed) return;
|
|
117
144
|
|
|
@@ -122,8 +149,15 @@ export class ObservedOutboundTrack<Kind extends MediaKind> extends EventEmitter
|
|
|
122
149
|
.finally(() => this.emit('close'));
|
|
123
150
|
}
|
|
124
151
|
|
|
125
|
-
public
|
|
126
|
-
|
|
152
|
+
public resetMetrics() {
|
|
153
|
+
this.bitrate = 0;
|
|
154
|
+
this.rttInMs = 0;
|
|
155
|
+
|
|
156
|
+
this.deltaLostPackets = 0;
|
|
157
|
+
this.deltaSentPackets = 0;
|
|
158
|
+
this.deltaSentBytes = 0;
|
|
159
|
+
this.deltaSentFrames = 0;
|
|
160
|
+
this.deltaEncodedFrames = 0;
|
|
127
161
|
}
|
|
128
162
|
|
|
129
163
|
public async update(sample: ObservedOutboundTrackStatsUpdate<Kind>, timestamp: number): Promise<void> {
|
|
@@ -150,13 +184,30 @@ export class ObservedOutboundTrack<Kind extends MediaKind> extends EventEmitter
|
|
|
150
184
|
const lastStat = this._stats.get(sample.ssrc);
|
|
151
185
|
const rttInMs = sample.roundTripTime ? sample.roundTripTime * 1000 : undefined;
|
|
152
186
|
const bitrate = ((sample.bytesSent ?? 0) - (lastStat?.bytesSent ?? 0)) * 8 / (elapsedTimeInMs / 1000);
|
|
153
|
-
|
|
154
|
-
|
|
187
|
+
const deltaLostPackets = (sample.packetsLost ?? 0) - (lastStat?.packetsLost ?? 0);
|
|
188
|
+
const deltaSentPackets = (sample.packetsSent ?? 0) - (lastStat?.packetsSent ?? 0);
|
|
189
|
+
const deltaSentBytes = (sample.bytesSent ?? 0) - (lastStat?.bytesSent ?? 0);
|
|
190
|
+
let deltaEncodedFrames: number | undefined;
|
|
191
|
+
let deltaSentFrames: number | undefined;
|
|
192
|
+
|
|
193
|
+
if (this.kind === 'video') {
|
|
194
|
+
const videoSample = sample as OutboundVideoTrack;
|
|
195
|
+
const lastVideoStats = lastStat as OutboundVideoTrack | undefined;
|
|
196
|
+
|
|
197
|
+
deltaEncodedFrames = (videoSample.framesEncoded ?? 0) - (lastVideoStats?.framesEncoded ?? 0);
|
|
198
|
+
deltaSentFrames = (videoSample.framesSent ?? 0) - (lastVideoStats?.framesSent ?? 0);
|
|
199
|
+
}
|
|
155
200
|
const stats: ObservedOutboundTrackStats<Kind> = {
|
|
156
201
|
...sample,
|
|
157
202
|
rttInMs,
|
|
158
203
|
bitrate,
|
|
159
204
|
ssrc: sample.ssrc,
|
|
205
|
+
|
|
206
|
+
deltaLostPackets,
|
|
207
|
+
deltaSentPackets,
|
|
208
|
+
deltaSentBytes,
|
|
209
|
+
deltaEncodedFrames,
|
|
210
|
+
deltaSentFrames,
|
|
160
211
|
};
|
|
161
212
|
|
|
162
213
|
this._stats.set(sample.ssrc, stats);
|
|
@@ -164,6 +215,16 @@ export class ObservedOutboundTrack<Kind extends MediaKind> extends EventEmitter
|
|
|
164
215
|
this.bitrate = [ ...this._stats.values() ].reduce((acc, stat) => acc + stat.bitrate, 0);
|
|
165
216
|
this.rttInMs = [ ...this._stats.values() ].reduce((acc, stat) => acc + (stat.rttInMs ?? 0), 0) / (this._stats.size || 1);
|
|
166
217
|
|
|
218
|
+
this.totalLostPackets = [ ...this._stats.values() ].reduce((acc, stat) => acc + (stat.packetsLost ?? 0), 0);
|
|
219
|
+
this.totalSentPackets = [ ...this._stats.values() ].reduce((acc, stat) => acc + (stat.packetsSent ?? 0), 0);
|
|
220
|
+
this.totalSentBytes = [ ...this._stats.values() ].reduce((acc, stat) => acc + (stat.bytesSent ?? 0), 0);
|
|
221
|
+
|
|
222
|
+
this.deltaEncodedFrames = [ ...this._stats.values() ].reduce((acc, stat) => acc + (stat.deltaEncodedFrames ?? 0), 0);
|
|
223
|
+
this.deltaSentFrames = [ ...this._stats.values() ].reduce((acc, stat) => acc + (stat.deltaSentFrames ?? 0), 0);
|
|
224
|
+
this.deltaLostPackets = [ ...this._stats.values() ].reduce((acc, stat) => acc + stat.deltaLostPackets, 0);
|
|
225
|
+
this.deltaSentPackets = [ ...this._stats.values() ].reduce((acc, stat) => acc + stat.deltaSentPackets, 0);
|
|
226
|
+
this.deltaSentBytes = [ ...this._stats.values() ].reduce((acc, stat) => acc + stat.deltaSentBytes, 0);
|
|
227
|
+
|
|
167
228
|
// setting up sfu connection as it is not always available at the first sample
|
|
168
229
|
if (sample.sfuStreamId && !this._model.sfuStreamId) {
|
|
169
230
|
this._model.sfuStreamId = sample.sfuStreamId;
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { EventEmitter } from 'events';
|
|
2
2
|
import * as Models from './models/Models';
|
|
3
3
|
import { StorageProvider } from './storages/StorageProvider';
|
|
4
|
-
import { PeerConnectionTransport } from '@observertc/sample-schemas-js';
|
|
4
|
+
import { IceLocalCandidate, IceRemoteCandidate, PeerConnectionTransport } from '@observertc/sample-schemas-js';
|
|
5
5
|
import { ObservedClient } from './ObservedClient';
|
|
6
6
|
import { ObservedInboundTrack, ObservedInboundTrackConfig } from './ObservedInboundTrack';
|
|
7
7
|
import { ObservedOutboundTrack, ObservedOutboundTrackConfig } from './ObservedOutboundTrack';
|
|
8
8
|
import { PeerConnectionTransportReport } from '@observertc/report-schemas-js';
|
|
9
9
|
import { createSingleExecutor } from './common/SingleExecutor';
|
|
10
|
+
import { ObservedICE } from './ObservedICE';
|
|
10
11
|
|
|
11
12
|
export type ObservedPeerConnectionEvents = {
|
|
12
13
|
update: [],
|
|
@@ -15,6 +16,10 @@ export type ObservedPeerConnectionEvents = {
|
|
|
15
16
|
newinboudvideotrack: [ObservedInboundTrack<'video'>],
|
|
16
17
|
newoutboundaudiotrack: [ObservedOutboundTrack<'audio'>],
|
|
17
18
|
newoutboundvideotrack: [ObservedOutboundTrack<'video'>],
|
|
19
|
+
|
|
20
|
+
iceconnectionstatechange: [string],
|
|
21
|
+
icegatheringstatechange: [string],
|
|
22
|
+
connectionstatechange: [string],
|
|
18
23
|
};
|
|
19
24
|
|
|
20
25
|
export type ObservedPeerConnectionConfig = {
|
|
@@ -51,10 +56,51 @@ export class ObservedPeerConnection extends EventEmitter {
|
|
|
51
56
|
return new ObservedPeerConnection(model, client, storageProvider);
|
|
52
57
|
}
|
|
53
58
|
|
|
59
|
+
public created = Date.now();
|
|
60
|
+
|
|
61
|
+
public totalInboundPacketsLost = 0;
|
|
62
|
+
public totalInboundPacketsReceived = 0;
|
|
63
|
+
public totalOutboundPacketsSent = 0;
|
|
64
|
+
public totalDataChannelBytesSent = 0;
|
|
65
|
+
public totalDataChannelBytesReceived = 0;
|
|
66
|
+
|
|
67
|
+
public totalSentAudioBytes = 0;
|
|
68
|
+
public totalSentVideoBytes = 0;
|
|
69
|
+
public totalSentAudioPackets = 0;
|
|
70
|
+
public totalSentVideoPackets = 0;
|
|
71
|
+
public totalReceivedAudioPacktes = 0;
|
|
72
|
+
public totalReceivedVideoPackets = 0;
|
|
73
|
+
public totalReceivedAudioBytes = 0;
|
|
74
|
+
public totalReceivedVideoBytes = 0;
|
|
75
|
+
|
|
76
|
+
public deltaInboundLostPackets = 0;
|
|
77
|
+
public deltaInboundReceivedPackets = 0;
|
|
78
|
+
public deltaOutboundSentPackets = 0;
|
|
79
|
+
public deltaDataChannelBytesSent = 0;
|
|
80
|
+
public deltaDataChannelBytesReceived = 0;
|
|
81
|
+
public deltaInboundReceivedBytes = 0;
|
|
82
|
+
public deltaOutboundSentBytes = 0;
|
|
83
|
+
|
|
84
|
+
public deltaReceivedAudioBytes = 0;
|
|
85
|
+
public deltaReceivedVideoBytes = 0;
|
|
86
|
+
public deltaReceivedAudioPackets = 0;
|
|
87
|
+
public deltaReceivedVideoPackets = 0;
|
|
88
|
+
public deltaSentAudioBytes = 0;
|
|
89
|
+
public deltaSentVideoBytes = 0;
|
|
90
|
+
public sendingAudioBitrate = 0;
|
|
91
|
+
public sendingVideoBitrate = 0;
|
|
92
|
+
public receivingAudioBitrate = 0;
|
|
93
|
+
public receivingVideoBitrate = 0;
|
|
94
|
+
|
|
95
|
+
public avgRttInS?: number;
|
|
96
|
+
|
|
54
97
|
private _closed = false;
|
|
55
98
|
private _updated = Date.now();
|
|
56
99
|
private _sample?: ObservedPeerConnectionStats;
|
|
100
|
+
private _selectedLocalIceCandidate?: IceLocalCandidate;
|
|
101
|
+
private _selectedRemoteIceCandidate?: IceRemoteCandidate;
|
|
57
102
|
|
|
103
|
+
public readonly ICE = ObservedICE.create(this);
|
|
58
104
|
private readonly _inboundAudioTracks = new Map<string, ObservedInboundTrack<'audio'>>();
|
|
59
105
|
private readonly _inboundVideoTracks = new Map<string, ObservedInboundTrack<'video'>>();
|
|
60
106
|
private readonly _outboundAudioTracks = new Map<string, ObservedOutboundTrack<'audio'>>();
|
|
@@ -86,6 +132,14 @@ export class ObservedPeerConnection extends EventEmitter {
|
|
|
86
132
|
return this._updated;
|
|
87
133
|
}
|
|
88
134
|
|
|
135
|
+
public get selectedLocalIceCandidate() {
|
|
136
|
+
return this._selectedLocalIceCandidate;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
public get selectedRemoteIceCandidate() {
|
|
140
|
+
return this._selectedRemoteIceCandidate;
|
|
141
|
+
}
|
|
142
|
+
|
|
89
143
|
public get inboundAudioTracks(): ReadonlyMap<string, ObservedInboundTrack<'audio'>> {
|
|
90
144
|
return this._inboundAudioTracks;
|
|
91
145
|
}
|
|
@@ -102,6 +156,10 @@ export class ObservedPeerConnection extends EventEmitter {
|
|
|
102
156
|
return this._outboundVideoTracks;
|
|
103
157
|
}
|
|
104
158
|
|
|
159
|
+
public get uptimeInMs() {
|
|
160
|
+
return this._updated - this.created;
|
|
161
|
+
}
|
|
162
|
+
|
|
105
163
|
public get closed() {
|
|
106
164
|
return this._closed;
|
|
107
165
|
}
|
|
@@ -120,6 +178,119 @@ export class ObservedPeerConnection extends EventEmitter {
|
|
|
120
178
|
.finally(() => this.emit('close'));
|
|
121
179
|
}
|
|
122
180
|
|
|
181
|
+
public getTrack(trackId: string): ObservedInboundTrack<'audio'> | ObservedInboundTrack<'video'> | ObservedOutboundTrack<'audio'> | ObservedOutboundTrack<'video'> | undefined {
|
|
182
|
+
return this._inboundAudioTracks.get(trackId) ?? this._inboundVideoTracks.get(trackId) ?? this._outboundAudioTracks.get(trackId) ?? this._outboundVideoTracks.get(trackId);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
public resetMetrics() {
|
|
186
|
+
this.deltaInboundLostPackets = 0;
|
|
187
|
+
this.deltaInboundReceivedPackets = 0;
|
|
188
|
+
this.deltaOutboundSentPackets = 0;
|
|
189
|
+
|
|
190
|
+
this.deltaDataChannelBytesSent = 0;
|
|
191
|
+
this.deltaDataChannelBytesReceived = 0;
|
|
192
|
+
this.deltaReceivedAudioBytes = 0;
|
|
193
|
+
this.deltaReceivedVideoBytes = 0;
|
|
194
|
+
this.deltaSentAudioBytes = 0;
|
|
195
|
+
this.deltaSentVideoBytes = 0;
|
|
196
|
+
|
|
197
|
+
this._inboundAudioTracks.forEach((track) => track.resetMetrics());
|
|
198
|
+
this._inboundVideoTracks.forEach((track) => track.resetMetrics());
|
|
199
|
+
this._outboundAudioTracks.forEach((track) => track.resetMetrics());
|
|
200
|
+
this._outboundVideoTracks.forEach((track) => track.resetMetrics());
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
public updateMetrics() {
|
|
204
|
+
this.totalInboundPacketsLost = 0;
|
|
205
|
+
this.totalInboundPacketsReceived = 0;
|
|
206
|
+
this.totalOutboundPacketsSent = 0;
|
|
207
|
+
this.totalDataChannelBytesSent = 0;
|
|
208
|
+
this.totalDataChannelBytesReceived = 0;
|
|
209
|
+
|
|
210
|
+
this.totalSentAudioBytes = 0;
|
|
211
|
+
this.totalSentVideoBytes = 0;
|
|
212
|
+
this.totalReceivedAudioBytes = 0;
|
|
213
|
+
this.totalReceivedVideoBytes = 0;
|
|
214
|
+
this.sendingAudioBitrate = 0;
|
|
215
|
+
this.sendingVideoBitrate = 0;
|
|
216
|
+
this.receivingAudioBitrate = 0;
|
|
217
|
+
this.receivingVideoBitrate = 0;
|
|
218
|
+
|
|
219
|
+
let sumRttInS = 0;
|
|
220
|
+
|
|
221
|
+
this._inboundAudioTracks.forEach((track) => {
|
|
222
|
+
this.deltaInboundLostPackets += track.deltaLostPackets;
|
|
223
|
+
this.deltaInboundReceivedPackets += track.deltaReceivedPackets;
|
|
224
|
+
this.deltaInboundReceivedBytes += track.deltaBytesReceived;
|
|
225
|
+
|
|
226
|
+
this.deltaReceivedAudioBytes += track.deltaBytesReceived;
|
|
227
|
+
this.deltaReceivedAudioPackets += track.deltaReceivedPackets;
|
|
228
|
+
|
|
229
|
+
this.receivingAudioBitrate += track.bitrate;
|
|
230
|
+
|
|
231
|
+
this.totalInboundPacketsLost += track.totalLostPackets;
|
|
232
|
+
this.totalInboundPacketsReceived += track.totalReceivedPackets;
|
|
233
|
+
this.totalReceivedAudioBytes += track.totalBytesReceived;
|
|
234
|
+
this.totalReceivedAudioPacktes += track.totalReceivedPackets;
|
|
235
|
+
|
|
236
|
+
sumRttInS = (track.rttInMs ?? 0) / 1000.0;
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
this._inboundVideoTracks.forEach((track) => {
|
|
240
|
+
this.deltaInboundLostPackets += track.deltaLostPackets;
|
|
241
|
+
this.deltaInboundReceivedPackets += track.deltaReceivedPackets;
|
|
242
|
+
this.deltaInboundReceivedBytes += track.deltaBytesReceived;
|
|
243
|
+
|
|
244
|
+
this.deltaReceivedVideoBytes += track.deltaBytesReceived;
|
|
245
|
+
this.deltaReceivedVideoPackets += track.deltaReceivedPackets;
|
|
246
|
+
|
|
247
|
+
this.receivingVideoBitrate += track.bitrate;
|
|
248
|
+
|
|
249
|
+
this.totalInboundPacketsLost += track.totalLostPackets;
|
|
250
|
+
this.totalInboundPacketsReceived += track.totalReceivedPackets;
|
|
251
|
+
this.totalReceivedVideoBytes += track.totalBytesReceived;
|
|
252
|
+
this.totalReceivedVideoPackets += track.totalReceivedPackets;
|
|
253
|
+
|
|
254
|
+
sumRttInS = (track.rttInMs ?? 0) / 1000.0;
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
this._outboundAudioTracks.forEach((track) => {
|
|
258
|
+
this.deltaOutboundSentPackets += track.deltaSentPackets;
|
|
259
|
+
this.deltaOutboundSentBytes += track.deltaSentBytes;
|
|
260
|
+
|
|
261
|
+
this.deltaSentAudioBytes += track.deltaSentBytes;
|
|
262
|
+
this.deltaSentAudioBytes += track.deltaSentPackets;
|
|
263
|
+
|
|
264
|
+
this.sendingAudioBitrate += track.bitrate;
|
|
265
|
+
|
|
266
|
+
this.totalOutboundPacketsSent += track.totalSentPackets;
|
|
267
|
+
this.totalSentAudioBytes += track.totalSentBytes;
|
|
268
|
+
this.totalSentAudioPackets += track.totalSentPackets;
|
|
269
|
+
|
|
270
|
+
sumRttInS = (track.rttInMs ?? 0) / 1000.0;
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
this._outboundVideoTracks.forEach((track) => {
|
|
274
|
+
this.deltaOutboundSentPackets += track.deltaSentPackets;
|
|
275
|
+
this.deltaOutboundSentBytes += track.deltaSentBytes;
|
|
276
|
+
|
|
277
|
+
this.deltaSentVideoBytes += track.deltaSentBytes;
|
|
278
|
+
this.deltaSentVideoBytes += track.deltaSentPackets;
|
|
279
|
+
|
|
280
|
+
this.sendingVideoBitrate += track.bitrate;
|
|
281
|
+
|
|
282
|
+
this.totalOutboundPacketsSent += track.totalSentPackets;
|
|
283
|
+
this.totalSentVideoBytes += track.totalSentBytes;
|
|
284
|
+
this.totalSentVideoPackets += track.totalSentPackets;
|
|
285
|
+
|
|
286
|
+
sumRttInS = (track.rttInMs ?? 0) / 1000.0;
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
const nrOfBelongings = this._inboundAudioTracks.size + this._inboundVideoTracks.size + this._outboundAudioTracks.size + this._outboundVideoTracks.size;
|
|
290
|
+
|
|
291
|
+
this.avgRttInS = 0 < nrOfBelongings ? sumRttInS / nrOfBelongings : undefined;
|
|
292
|
+
}
|
|
293
|
+
|
|
123
294
|
public update(sample: PeerConnectionTransport, timestamp: number) {
|
|
124
295
|
if (this._closed) return;
|
|
125
296
|
if (sample.peerConnectionId !== this._model.peerConnectionId) throw new Error(`TransportId mismatch. PeerConnectionId: ${ this._model.peerConnectionId } TransportId: ${ sample.transportId}`);
|
|
@@ -144,6 +315,11 @@ export class ObservedPeerConnection extends EventEmitter {
|
|
|
144
315
|
this.emit('update');
|
|
145
316
|
}
|
|
146
317
|
|
|
318
|
+
public updateSelectedCandidate(localCandidate: IceLocalCandidate, remoteCandidate: IceRemoteCandidate) {
|
|
319
|
+
this._selectedLocalIceCandidate = localCandidate;
|
|
320
|
+
this._selectedRemoteIceCandidate = remoteCandidate;
|
|
321
|
+
}
|
|
322
|
+
|
|
147
323
|
public async createInboundAudioTrack(config: Omit<ObservedInboundTrackConfig<'audio'>, 'kind'>): Promise<ObservedInboundTrack<'audio'>> {
|
|
148
324
|
if (this._closed) throw new Error(`PeerConnection ${this.peerConnectionId} is closed`);
|
|
149
325
|
|
|
@@ -152,6 +328,13 @@ export class ObservedPeerConnection extends EventEmitter {
|
|
|
152
328
|
trackId: config.trackId,
|
|
153
329
|
}, this, this._storageProvider);
|
|
154
330
|
|
|
331
|
+
const pendingTracksTimestamp = this.client.ωpendingCreatedTracksTimestamp.get(result.trackId);
|
|
332
|
+
|
|
333
|
+
if (pendingTracksTimestamp) {
|
|
334
|
+
this.client.ωpendingCreatedTracksTimestamp.delete(result.trackId);
|
|
335
|
+
result.created = pendingTracksTimestamp;
|
|
336
|
+
}
|
|
337
|
+
|
|
155
338
|
result.on('close', () => {
|
|
156
339
|
this._inboundAudioTracks.delete(result.trackId);
|
|
157
340
|
this._model.inboundTrackIds = this._model.inboundTrackIds.filter((id) => id !== result.trackId);
|
|
@@ -173,6 +356,13 @@ export class ObservedPeerConnection extends EventEmitter {
|
|
|
173
356
|
kind: 'video',
|
|
174
357
|
trackId: config.trackId,
|
|
175
358
|
}, this, this._storageProvider);
|
|
359
|
+
|
|
360
|
+
const pendingTracksTimestamp = this.client.ωpendingCreatedTracksTimestamp.get(result.trackId);
|
|
361
|
+
|
|
362
|
+
if (pendingTracksTimestamp) {
|
|
363
|
+
this.client.ωpendingCreatedTracksTimestamp.delete(result.trackId);
|
|
364
|
+
result.created = pendingTracksTimestamp;
|
|
365
|
+
}
|
|
176
366
|
|
|
177
367
|
result.on('close', () => {
|
|
178
368
|
this._inboundVideoTracks.delete(result.trackId);
|
|
@@ -196,6 +386,13 @@ export class ObservedPeerConnection extends EventEmitter {
|
|
|
196
386
|
trackId: config.trackId,
|
|
197
387
|
}, this, this._storageProvider);
|
|
198
388
|
|
|
389
|
+
const pendingTracksTimestamp = this.client.ωpendingCreatedTracksTimestamp.get(result.trackId);
|
|
390
|
+
|
|
391
|
+
if (pendingTracksTimestamp) {
|
|
392
|
+
this.client.ωpendingCreatedTracksTimestamp.delete(result.trackId);
|
|
393
|
+
result.created = pendingTracksTimestamp;
|
|
394
|
+
}
|
|
395
|
+
|
|
199
396
|
result.on('close', () => {
|
|
200
397
|
this._outboundAudioTracks.delete(result.trackId);
|
|
201
398
|
this._model.outboundTrackIds = this._model.outboundTrackIds.filter((id) => id !== result.trackId);
|
|
@@ -222,6 +419,13 @@ export class ObservedPeerConnection extends EventEmitter {
|
|
|
222
419
|
trackId: config.trackId,
|
|
223
420
|
}, this, this._storageProvider);
|
|
224
421
|
|
|
422
|
+
const pendingTracksTimestamp = this.client.ωpendingCreatedTracksTimestamp.get(result.trackId);
|
|
423
|
+
|
|
424
|
+
if (pendingTracksTimestamp) {
|
|
425
|
+
this.client.ωpendingCreatedTracksTimestamp.delete(result.trackId);
|
|
426
|
+
result.created = pendingTracksTimestamp;
|
|
427
|
+
}
|
|
428
|
+
|
|
225
429
|
result.on('close', () => {
|
|
226
430
|
this._outboundVideoTracks.delete(result.trackId);
|
|
227
431
|
result.sfuStreamId && this.client.call.sfuStreamIdToOutboundVideoTrack.set(result.sfuStreamId, result);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// eslint-disable-next-line no-shadow
|
|
2
|
+
|
|
3
|
+
export enum CallEventType {
|
|
4
|
+
CALL_STARTED = 'CALL_STARTED',
|
|
5
|
+
CALL_ENDED = 'CALL_ENDED',
|
|
6
|
+
CLIENT_JOINED = 'CLIENT_JOINED',
|
|
7
|
+
CLIENT_LEFT = 'CLIENT_LEFT',
|
|
8
|
+
PEER_CONNECTION_OPENED = 'PEER_CONNECTION_OPENED',
|
|
9
|
+
PEER_CONNECTION_CLOSED = 'PEER_CONNECTION_CLOSED',
|
|
10
|
+
MEDIA_TRACK_ADDED = 'MEDIA_TRACK_ADDED',
|
|
11
|
+
MEDIA_TRACK_REMOVED = 'MEDIA_TRACK_REMOVED',
|
|
12
|
+
MEDIA_TRACK_RESUMED = 'MEDIA_TRACK_RESUMED',
|
|
13
|
+
MEDIA_TRACK_MUTED = 'MEDIA_TRACK_MUTED',
|
|
14
|
+
MEDIA_TRACK_UNMUTED = 'MEDIA_TRACK_UNMUTED',
|
|
15
|
+
ICE_GATHERING_STATE_CHANGED = 'ICE_GATHERING_STATE_CHANGED',
|
|
16
|
+
PEER_CONNECTION_STATE_CHANGED = 'PEER_CONNECTION_STATE_CHANGED',
|
|
17
|
+
ICE_CONNECTION_STATE_CHANGED = 'ICE_CONNECTION_STATE_CHANGED',
|
|
18
|
+
DATA_CHANNEL_OPEN = 'DATA_CHANNEL_OPEN',
|
|
19
|
+
DATA_CHANNEL_CLOSED = 'DATA_CHANNEL_CLOSED',
|
|
20
|
+
DATA_CHANNEL_ERROR = 'DATA_CHANNEL_ERROR',
|
|
21
|
+
NEGOTIATION_NEEDED = 'NEGOTIATION_NEEDED',
|
|
22
|
+
SIGNALING_STATE_CHANGE = 'SIGNALING_STATE_CHANGE',
|
|
23
|
+
}
|
|
@@ -1,16 +1,6 @@
|
|
|
1
1
|
import { CallEventReport } from '@observertc/report-schemas-js';
|
|
2
|
+
import { CallEventType } from './CallEventType';
|
|
2
3
|
|
|
3
|
-
// eslint-disable-next-line no-shadow
|
|
4
|
-
export enum CallEventType {
|
|
5
|
-
CALL_STARTED = 'CALL_STARTED',
|
|
6
|
-
CALL_ENDED = 'CALL_ENDED',
|
|
7
|
-
CLIENT_JOINED = 'CLIENT_JOINED',
|
|
8
|
-
CLIENT_LEFT = 'CLIENT_LEFT',
|
|
9
|
-
PEER_CONNECTION_OPENED = 'PEER_CONNECTION_OPENED',
|
|
10
|
-
PEER_CONNECTION_CLOSED = 'PEER_CONNECTION_CLOSED',
|
|
11
|
-
MEDIA_TRACK_ADDED = 'MEDIA_TRACK_ADDED',
|
|
12
|
-
MEDIA_TRACK_REMOVED = 'MEDIA_TRACK_REMOVED',
|
|
13
|
-
}
|
|
14
4
|
export function createCallStartedEventReport(
|
|
15
5
|
serviceId: string,
|
|
16
6
|
roomId: string,
|