|
@@ -17,11 +17,56 @@ const logger = getLogger(__filename);
|
17
|
17
|
*/
|
18
|
18
|
const DEFAULT_RTC_MUTE_TIMEOUT = 2000;
|
19
|
19
|
|
|
20
|
+/**
|
|
21
|
+ * The time to wait a track to be restored. Track which was out of lastN
|
|
22
|
+ * should be inactive and when entering lastN it becomes restoring and when
|
|
23
|
+ * data is received from bridge it will become active, but if no data is
|
|
24
|
+ * received for some time we set status of that participant connection to
|
|
25
|
+ * interrupted.
|
|
26
|
+ * @type {number}
|
|
27
|
+ */
|
|
28
|
+const DEFAULT_RESTORING_TIMEOUT = 5000;
|
|
29
|
+
|
|
30
|
+/**
|
|
31
|
+ * Participant connection statuses.
|
|
32
|
+ *
|
|
33
|
+ * @type {{
|
|
34
|
+ * ACTIVE: string,
|
|
35
|
+ * INACTIVE: string,
|
|
36
|
+ * INTERRUPTED: string,
|
|
37
|
+ * RESTORING: string
|
|
38
|
+ * }}
|
|
39
|
+ */
|
|
40
|
+export const ParticipantConnectionStatus = {
|
|
41
|
+ /**
|
|
42
|
+ * Status indicating that connection is currently active.
|
|
43
|
+ */
|
|
44
|
+ ACTIVE: 'active',
|
|
45
|
+
|
|
46
|
+ /**
|
|
47
|
+ * Status indicating that connection is currently inactive.
|
|
48
|
+ * Inactive means the connection was stopped on purpose from the bridge,
|
|
49
|
+ * like exiting lastN or adaptivity decided to drop video because of not
|
|
50
|
+ * enough bandwidth.
|
|
51
|
+ */
|
|
52
|
+ INACTIVE: 'inactive',
|
|
53
|
+
|
|
54
|
+ /**
|
|
55
|
+ * Status indicating that connection is currently interrupted.
|
|
56
|
+ */
|
|
57
|
+ INTERRUPTED: 'interrupted',
|
|
58
|
+
|
|
59
|
+ /**
|
|
60
|
+ * Status indicating that connection is currently restoring.
|
|
61
|
+ */
|
|
62
|
+ RESTORING: 'restoring'
|
|
63
|
+};
|
|
64
|
+
|
20
|
65
|
/**
|
21
|
66
|
* Class is responsible for emitting
|
22
|
67
|
* JitsiConferenceEvents.PARTICIPANT_CONN_STATUS_CHANGED events.
|
23
|
68
|
*/
|
24
|
|
-export default class ParticipantConnectionStatus {
|
|
69
|
+export default class ParticipantConnectionStatusHandler {
|
25
|
70
|
/**
|
26
|
71
|
* Creates new instance of <tt>ParticipantConnectionStatus</tt>.
|
27
|
72
|
*
|
|
@@ -86,6 +131,27 @@ export default class ParticipantConnectionStatus {
|
86
|
131
|
*/
|
87
|
132
|
this.rtcMutedTimestamp = { };
|
88
|
133
|
logger.info(`RtcMuteTimeout set to: ${this.rtcMuteTimeout}`);
|
|
134
|
+
|
|
135
|
+ /**
|
|
136
|
+ * This map holds the timestamps indicating when participant's video
|
|
137
|
+ * entered lastN set. Participants entering lastN will have connection
|
|
138
|
+ * status restoring and when we start receiving video will become
|
|
139
|
+ * active, but if video is not received for certain time
|
|
140
|
+ * {@link DEFAULT_RESTORING_TIMEOUT} that participant connection status
|
|
141
|
+ * will become interrupted.
|
|
142
|
+ *
|
|
143
|
+ * @type {Map<string, number>}
|
|
144
|
+ */
|
|
145
|
+ this.enteredLastNTimestamp = new Map();
|
|
146
|
+
|
|
147
|
+ /**
|
|
148
|
+ * A map of the "endpoint ID"(which corresponds to the resource part
|
|
149
|
+ * of MUC JID(nickname)) to the restoring timeout callback IDs
|
|
150
|
+ * scheduled using window.setTimeout.
|
|
151
|
+ *
|
|
152
|
+ * @type {Map<string, number>}
|
|
153
|
+ */
|
|
154
|
+ this.restoringTimers = new Map();
|
89
|
155
|
}
|
90
|
156
|
|
91
|
157
|
/**
|
|
@@ -135,6 +201,11 @@ export default class ParticipantConnectionStatus {
|
135
|
201
|
this._onSignallingMuteChanged
|
136
|
202
|
= this.onSignallingMuteChanged.bind(this);
|
137
|
203
|
}
|
|
204
|
+
|
|
205
|
+ this._onLastNChanged = this._onLastNChanged.bind(this);
|
|
206
|
+ this.conference.on(
|
|
207
|
+ JitsiConferenceEvents.LAST_N_ENDPOINTS_CHANGED,
|
|
208
|
+ this._onLastNChanged);
|
138
|
209
|
}
|
139
|
210
|
|
140
|
211
|
/**
|
|
@@ -163,6 +234,10 @@ export default class ParticipantConnectionStatus {
|
163
|
234
|
this._onRemoteTrackRemoved);
|
164
|
235
|
}
|
165
|
236
|
|
|
237
|
+ this.conference.off(
|
|
238
|
+ JitsiConferenceEvents.LAST_N_ENDPOINTS_CHANGED,
|
|
239
|
+ this._onLastNChanged);
|
|
240
|
+
|
166
|
241
|
this.conference.off(
|
167
|
242
|
JitsiConferenceEvents.P2P_STATUS, this._onP2PStatus);
|
168
|
243
|
|
|
@@ -181,8 +256,8 @@ export default class ParticipantConnectionStatus {
|
181
|
256
|
* Handles RTCEvents.ENDPOINT_CONN_STATUS_CHANGED triggered when we receive
|
182
|
257
|
* notification over the data channel from the bridge about endpoint's
|
183
|
258
|
* connection status update.
|
184
|
|
- * @param endpointId {string} the endpoint ID(MUC nickname/resource JID)
|
185
|
|
- * @param isActive {boolean} true if the connection is OK or false otherwise
|
|
259
|
+ * @param {string} endpointId the endpoint ID(MUC nickname/resource JID)
|
|
260
|
+ * @param {boolean} isActive true if the connection is OK or false otherwise
|
186
|
261
|
*/
|
187
|
262
|
onEndpointConnStatusChanged(endpointId, isActive) {
|
188
|
263
|
|
|
@@ -199,16 +274,16 @@ export default class ParticipantConnectionStatus {
|
199
|
274
|
}
|
200
|
275
|
|
201
|
276
|
/**
|
202
|
|
- *
|
203
|
|
- * @param participant
|
|
277
|
+ * Changes connection status.
|
|
278
|
+ * @param {JitsiParticipant} participant
|
204
|
279
|
* @param newStatus
|
205
|
280
|
*/
|
206
|
281
|
_changeConnectionStatus(participant, newStatus) {
|
207
|
|
- if (participant.isConnectionActive() !== newStatus) {
|
|
282
|
+ if (participant.getConnectionStatus() !== newStatus) {
|
208
|
283
|
|
209
|
284
|
const endpointId = participant.getId();
|
210
|
285
|
|
211
|
|
- participant._setIsConnectionActive(newStatus);
|
|
286
|
+ participant._setConnectionStatus(newStatus);
|
212
|
287
|
|
213
|
288
|
logger.debug(
|
214
|
289
|
`Emit endpoint conn status(${Date.now()}) ${endpointId}: ${
|
|
@@ -236,7 +311,7 @@ export default class ParticipantConnectionStatus {
|
236
|
311
|
* Reset the postponed "connection interrupted" event which was previously
|
237
|
312
|
* scheduled as a timeout on RTC 'onmute' event.
|
238
|
313
|
*
|
239
|
|
- * @param participantId the participant for which the "connection
|
|
314
|
+ * @param {string} participantId the participant for which the "connection
|
240
|
315
|
* interrupted" timeout was scheduled
|
241
|
316
|
*/
|
242
|
317
|
clearTimeout(participantId) {
|
|
@@ -248,8 +323,8 @@ export default class ParticipantConnectionStatus {
|
248
|
323
|
|
249
|
324
|
/**
|
250
|
325
|
* Clears the timestamp of the RTC muted event for participant's video track
|
251
|
|
- * @param participantId the id of the conference participant which is
|
252
|
|
- * the same as the Colibri endpoint ID of the video channel allocated for
|
|
326
|
+ * @param {string} participantId the id of the conference participant which
|
|
327
|
+ * is the same as the Colibri endpoint ID of the video channel allocated for
|
253
|
328
|
* the user on the videobridge.
|
254
|
329
|
*/
|
255
|
330
|
clearRtcMutedTimestamp(participantId) {
|
|
@@ -380,18 +455,117 @@ export default class ParticipantConnectionStatus {
|
380
|
455
|
isConnActiveByJvb = true;
|
381
|
456
|
}
|
382
|
457
|
|
383
|
|
- const isConnectionActive
|
384
|
|
- = isConnActiveByJvb
|
385
|
|
- && (isVideoMuted || (isInLastN && !isVideoTrackFrozen));
|
|
458
|
+ let newState = ParticipantConnectionStatus.INACTIVE;
|
|
459
|
+
|
|
460
|
+ if (isConnActiveByJvb) {
|
|
461
|
+ if (isInLastN) {
|
|
462
|
+ if (isVideoTrackFrozen) {
|
|
463
|
+ newState = this._isRestoringTimedout(id)
|
|
464
|
+ ? ParticipantConnectionStatus.INTERRUPTED
|
|
465
|
+ : ParticipantConnectionStatus.RESTORING;
|
|
466
|
+ } else {
|
|
467
|
+ newState = ParticipantConnectionStatus.ACTIVE;
|
|
468
|
+ }
|
|
469
|
+ }
|
|
470
|
+ } else {
|
|
471
|
+ // when there is a connection problem signaled from jvb
|
|
472
|
+ // it means no media was flowing for at least 15secs, so everything
|
|
473
|
+ // should be interrupted, when in p2p mode we will never end up here
|
|
474
|
+ newState = ParticipantConnectionStatus.INTERRUPTED;
|
|
475
|
+ }
|
|
476
|
+
|
|
477
|
+ // if the new state is not restoring clear timers and timestamps
|
|
478
|
+ // that we use to track the restoring state
|
|
479
|
+ if (newState !== ParticipantConnectionStatus.RESTORING) {
|
|
480
|
+ this._clearRestoringTimer(id);
|
|
481
|
+ }
|
386
|
482
|
|
387
|
483
|
logger.debug(
|
388
|
|
- `Figure out conn status, is video muted: ${isVideoMuted
|
|
484
|
+ `Figure out conn status for ${id}, is video muted: ${isVideoMuted
|
389
|
485
|
} is active(jvb): ${isConnActiveByJvb
|
390
|
486
|
} video track frozen: ${isVideoTrackFrozen
|
391
|
487
|
} is in last N: ${isInLastN
|
392
|
|
- } => ${isConnectionActive}`);
|
|
488
|
+ } currentStatus => newStatus:
|
|
489
|
+ ${participant.getConnectionStatus()} => ${newState}`);
|
|
490
|
+
|
|
491
|
+ this._changeConnectionStatus(participant, newState);
|
|
492
|
+ }
|
|
493
|
+
|
|
494
|
+ /**
|
|
495
|
+ * On change in Last N set check all leaving and entering participants to
|
|
496
|
+ * change their corresponding statuses.
|
|
497
|
+ *
|
|
498
|
+ * @param {Array<string>} leavingLastN array of ids leaving lastN.
|
|
499
|
+ * @param {Array<string>} enteringLastN array of ids entering lastN.
|
|
500
|
+ * @private
|
|
501
|
+ */
|
|
502
|
+ _onLastNChanged(leavingLastN = [], enteringLastN = []) {
|
|
503
|
+ for (const id of leavingLastN) {
|
|
504
|
+ this.enteredLastNTimestamp.delete(id);
|
|
505
|
+ this._clearRestoringTimer(id);
|
|
506
|
+ this.figureOutConnectionStatus(id);
|
|
507
|
+ }
|
|
508
|
+ for (const id of enteringLastN) {
|
|
509
|
+ // store the timestamp this id is entering lastN
|
|
510
|
+ this.enteredLastNTimestamp.set(id, Date.now());
|
|
511
|
+
|
|
512
|
+ this.figureOutConnectionStatus(id);
|
|
513
|
+ }
|
|
514
|
+ }
|
|
515
|
+
|
|
516
|
+ /**
|
|
517
|
+ * Clears the restoring timer for participant's video track and the
|
|
518
|
+ * timestamp for entering lastN.
|
|
519
|
+ *
|
|
520
|
+ * @param {string} participantId the id of the conference participant which
|
|
521
|
+ * is the same as the Colibri endpoint ID of the video channel allocated for
|
|
522
|
+ * the user on the videobridge.
|
|
523
|
+ */
|
|
524
|
+ _clearRestoringTimer(participantId) {
|
|
525
|
+ const rTimer = this.restoringTimers.get(participantId);
|
|
526
|
+
|
|
527
|
+ if (rTimer) {
|
|
528
|
+ clearTimeout(rTimer);
|
|
529
|
+ this.restoringTimers.delete(participantId);
|
|
530
|
+ }
|
|
531
|
+ }
|
|
532
|
+
|
|
533
|
+ /**
|
|
534
|
+ * Checks whether a track had stayed enough in restoring state, compares
|
|
535
|
+ * current time and the time the track entered in lastN. If it hasn't
|
|
536
|
+ * timedout and there is no timer added, add new timer in order to give it
|
|
537
|
+ * more time to become active or mark it as interrupted on next check.
|
|
538
|
+ *
|
|
539
|
+ * @param {string} participantId the id of the conference participant which
|
|
540
|
+ * is the same as the Colibri endpoint ID of the video channel allocated for
|
|
541
|
+ * the user on the videobridge.
|
|
542
|
+ * @returns {boolean} <tt>true</tt> if the track was in restoring state
|
|
543
|
+ * more than the timeout ({@link DEFAULT_RESTORING_TIMEOUT}.) in order to
|
|
544
|
+ * set its status to interrupted.
|
|
545
|
+ * @private
|
|
546
|
+ */
|
|
547
|
+ _isRestoringTimedout(participantId) {
|
|
548
|
+ const enteredLastNTimestamp
|
|
549
|
+ = this.enteredLastNTimestamp.get(participantId);
|
|
550
|
+
|
|
551
|
+ if (enteredLastNTimestamp
|
|
552
|
+ && (Date.now() - enteredLastNTimestamp)
|
|
553
|
+ >= DEFAULT_RESTORING_TIMEOUT) {
|
|
554
|
+ return true;
|
|
555
|
+ }
|
|
556
|
+
|
|
557
|
+ // still haven't reached timeout, if there is no timer scheduled,
|
|
558
|
+ // schedule one so we can track the restoring state and change it after
|
|
559
|
+ // reaching the timeout
|
|
560
|
+ const rTimer = this.restoringTimers.get(participantId);
|
|
561
|
+
|
|
562
|
+ if (!rTimer) {
|
|
563
|
+ this.restoringTimers.set(participantId, setTimeout(
|
|
564
|
+ () => this.figureOutConnectionStatus(participantId),
|
|
565
|
+ DEFAULT_RESTORING_TIMEOUT));
|
|
566
|
+ }
|
393
|
567
|
|
394
|
|
- this._changeConnectionStatus(participant, isConnectionActive);
|
|
568
|
+ return false;
|
395
|
569
|
}
|
396
|
570
|
|
397
|
571
|
/**
|