|
@@ -9,6 +9,14 @@ import Statistics from '../statistics/statistics';
|
9
|
9
|
|
10
|
10
|
const logger = getLogger(__filename);
|
11
|
11
|
|
|
12
|
+/**
|
|
13
|
+ * Default value of 500 milliseconds for
|
|
14
|
+ * {@link ParticipantConnectionStatus.outOfLastNTimeout}.
|
|
15
|
+ *
|
|
16
|
+ * @type {number}
|
|
17
|
+ */
|
|
18
|
+const DEFAULT_NOT_IN_LAST_N_TIMEOUT = 500;
|
|
19
|
+
|
12
|
20
|
/**
|
13
|
21
|
* Default value of 2000 milliseconds for
|
14
|
22
|
* {@link ParticipantConnectionStatus.rtcMuteTimeout}.
|
|
@@ -160,10 +168,13 @@ export default class ParticipantConnectionStatusHandler {
|
160
|
168
|
* @constructor
|
161
|
169
|
* @param {RTC} rtc the RTC service instance
|
162
|
170
|
* @param {JitsiConference} conference parent conference instance
|
163
|
|
- * @param {number} rtcMuteTimeout (optional) custom value for
|
|
171
|
+ * @param {Object} options
|
|
172
|
+ * @param {number} [options.rtcMuteTimeout=2000] custom value for
|
164
|
173
|
* {@link ParticipantConnectionStatus.rtcMuteTimeout}.
|
|
174
|
+ * @param {number} [options.outOfLastNTimeout=500] custom value for
|
|
175
|
+ * {@link ParticipantConnectionStatus.outOfLastNTimeout}.
|
165
|
176
|
*/
|
166
|
|
- constructor(rtc, conference, rtcMuteTimeout) {
|
|
177
|
+ constructor(rtc, conference, options) {
|
167
|
178
|
this.rtc = rtc;
|
168
|
179
|
this.conference = conference;
|
169
|
180
|
|
|
@@ -183,6 +194,21 @@ export default class ParticipantConnectionStatusHandler {
|
183
|
194
|
*/
|
184
|
195
|
this.connStatusFromJvb = { };
|
185
|
196
|
|
|
197
|
+ /**
|
|
198
|
+ * If video track frozen detection through RTC mute event is supported,
|
|
199
|
+ * we wait some time until video track is considered frozen. But because
|
|
200
|
+ * when the user falls out of last N it is expected for the video to
|
|
201
|
+ * freeze this timeout must be significantly reduced in "out of last N"
|
|
202
|
+ * case.
|
|
203
|
+ *
|
|
204
|
+ * Basically this value is used instead of {@link rtcMuteTimeout} when
|
|
205
|
+ * user is not in last N.
|
|
206
|
+ * @type {number}
|
|
207
|
+ */
|
|
208
|
+ this.outOfLastNTimeout
|
|
209
|
+ = typeof options.outOfLastNTimeout === 'number'
|
|
210
|
+ ? options.outOfLastNTimeout : DEFAULT_NOT_IN_LAST_N_TIMEOUT;
|
|
211
|
+
|
186
|
212
|
/**
|
187
|
213
|
* How long we're going to wait after the RTC video track muted event
|
188
|
214
|
* for the corresponding signalling mute event, before the connection
|
|
@@ -192,8 +218,8 @@ export default class ParticipantConnectionStatusHandler {
|
192
|
218
|
* @type {number} amount of time in milliseconds
|
193
|
219
|
*/
|
194
|
220
|
this.rtcMuteTimeout
|
195
|
|
- = typeof rtcMuteTimeout === 'number'
|
196
|
|
- ? rtcMuteTimeout : DEFAULT_RTC_MUTE_TIMEOUT;
|
|
221
|
+ = typeof options.rtcMuteTimeout === 'number'
|
|
222
|
+ ? options.rtcMuteTimeout : DEFAULT_RTC_MUTE_TIMEOUT;
|
197
|
223
|
|
198
|
224
|
/**
|
199
|
225
|
* This map holds a timestamp indicating when participant's video track
|
|
@@ -241,6 +267,18 @@ export default class ParticipantConnectionStatusHandler {
|
241
|
267
|
this.restoringTimers = new Map();
|
242
|
268
|
}
|
243
|
269
|
|
|
270
|
+ /**
|
|
271
|
+ * Gets the video frozen timeout for given user.
|
|
272
|
+ * @param {string} id endpoint/participant ID
|
|
273
|
+ * @return {number} how long are we going to wait since RTC video muted
|
|
274
|
+ * even, before a video track is considered frozen.
|
|
275
|
+ * @private
|
|
276
|
+ */
|
|
277
|
+ _getVideoFrozenTimeout(id) {
|
|
278
|
+ return this.rtc.isInLastN(id)
|
|
279
|
+ ? this.rtcMuteTimeout : this.outOfLastNTimeout;
|
|
280
|
+ }
|
|
281
|
+
|
244
|
282
|
/**
|
245
|
283
|
* Initializes <tt>ParticipantConnectionStatus</tt> and bind required event
|
246
|
284
|
* listeners.
|
|
@@ -293,6 +331,11 @@ export default class ParticipantConnectionStatusHandler {
|
293
|
331
|
this.conference.on(
|
294
|
332
|
JitsiConferenceEvents.LAST_N_ENDPOINTS_CHANGED,
|
295
|
333
|
this._onLastNChanged);
|
|
334
|
+
|
|
335
|
+ this._onLastNValueChanged
|
|
336
|
+ = this.refreshConnectionStatusForAll.bind(this);
|
|
337
|
+ this.rtc.on(
|
|
338
|
+ RTCEvents.LASTN_VALUE_CHANGED, this._onLastNValueChanged);
|
296
|
339
|
}
|
297
|
340
|
|
298
|
341
|
/**
|
|
@@ -325,6 +368,9 @@ export default class ParticipantConnectionStatusHandler {
|
325
|
368
|
JitsiConferenceEvents.LAST_N_ENDPOINTS_CHANGED,
|
326
|
369
|
this._onLastNChanged);
|
327
|
370
|
|
|
371
|
+ this.rtc.removeListener(
|
|
372
|
+ RTCEvents.LASTN_VALUE_CHANGED, this._onLastNValueChanged);
|
|
373
|
+
|
328
|
374
|
this.conference.off(
|
329
|
375
|
JitsiConferenceEvents.P2P_STATUS, this._onP2PStatus);
|
330
|
376
|
|
|
@@ -479,13 +525,14 @@ export default class ParticipantConnectionStatusHandler {
|
479
|
525
|
return false;
|
480
|
526
|
}
|
481
|
527
|
|
|
528
|
+ const id = participant.getId();
|
482
|
529
|
const hasAnyVideoRTCMuted = participant.hasAnyVideoTrackWebRTCMuted();
|
483
|
|
- const rtcMutedTimestamp
|
484
|
|
- = this.rtcMutedTimestamp[participant.getId()];
|
|
530
|
+ const rtcMutedTimestamp = this.rtcMutedTimestamp[id];
|
|
531
|
+ const timeout = this._getVideoFrozenTimeout(id);
|
485
|
532
|
|
486
|
533
|
return hasAnyVideoRTCMuted
|
487
|
534
|
&& typeof rtcMutedTimestamp === 'number'
|
488
|
|
- && (Date.now() - rtcMutedTimestamp) >= this.rtcMuteTimeout;
|
|
535
|
+ && (Date.now() - rtcMutedTimestamp) >= timeout;
|
489
|
536
|
}
|
490
|
537
|
|
491
|
538
|
/**
|
|
@@ -525,7 +572,11 @@ export default class ParticipantConnectionStatusHandler {
|
525
|
572
|
|
526
|
573
|
const inP2PMode = this.conference.isP2PActive();
|
527
|
574
|
const isRestoringTimedOut = this._isRestoringTimedout(id);
|
528
|
|
- const isVideoMuted = participant.isVideoMuted();
|
|
575
|
+ const audioOnlyMode = this.rtc.getLastN() === 0;
|
|
576
|
+
|
|
577
|
+ // NOTE Overriding videoMuted to true for audioOnlyMode should disable
|
|
578
|
+ // any detection based on video playback or the last N.
|
|
579
|
+ const isVideoMuted = participant.isVideoMuted() || audioOnlyMode;
|
529
|
580
|
const isVideoTrackFrozen = this.isVideoTrackFrozen(participant);
|
530
|
581
|
const isInLastN = this.rtc.isInLastN(id);
|
531
|
582
|
let isConnActiveByJvb = this.connStatusFromJvb[id];
|
|
@@ -576,6 +627,11 @@ export default class ParticipantConnectionStatusHandler {
|
576
|
627
|
* @private
|
577
|
628
|
*/
|
578
|
629
|
_onLastNChanged(leavingLastN = [], enteringLastN = []) {
|
|
630
|
+ const now = Date.now();
|
|
631
|
+
|
|
632
|
+ logger.debug(
|
|
633
|
+ 'leaving/entering lastN', leavingLastN, enteringLastN, now);
|
|
634
|
+
|
579
|
635
|
for (const id of leavingLastN) {
|
580
|
636
|
this.enteredLastNTimestamp.delete(id);
|
581
|
637
|
this._clearRestoringTimer(id);
|
|
@@ -583,8 +639,7 @@ export default class ParticipantConnectionStatusHandler {
|
583
|
639
|
}
|
584
|
640
|
for (const id of enteringLastN) {
|
585
|
641
|
// store the timestamp this id is entering lastN
|
586
|
|
- this.enteredLastNTimestamp.set(id, Date.now());
|
587
|
|
-
|
|
642
|
+ this.enteredLastNTimestamp.set(id, now);
|
588
|
643
|
this.figureOutConnectionStatus(id);
|
589
|
644
|
}
|
590
|
645
|
}
|
|
@@ -654,7 +709,7 @@ export default class ParticipantConnectionStatusHandler {
|
654
|
709
|
const participantId = track.getParticipantId();
|
655
|
710
|
const participant = this.conference.getParticipantById(participantId);
|
656
|
711
|
|
657
|
|
- logger.debug(`Detector track RTC muted: ${participantId}`);
|
|
712
|
+ logger.debug(`Detector track RTC muted: ${participantId}`, Date.now());
|
658
|
713
|
if (!participant) {
|
659
|
714
|
logger.error(`No participant for id: ${participantId}`);
|
660
|
715
|
|
|
@@ -666,11 +721,17 @@ export default class ParticipantConnectionStatusHandler {
|
666
|
721
|
// it some time, before the connection interrupted event is
|
667
|
722
|
// triggered.
|
668
|
723
|
this.clearTimeout(participantId);
|
|
724
|
+
|
|
725
|
+ // The timeout is reduced when user is not in the last N
|
|
726
|
+ const timeout = this._getVideoFrozenTimeout(participantId);
|
|
727
|
+
|
669
|
728
|
this.trackTimers[participantId] = window.setTimeout(() => {
|
670
|
|
- logger.debug(`RTC mute timeout for: ${participantId}`);
|
|
729
|
+ logger.debug(
|
|
730
|
+ `Set RTC mute timeout for: ${participantId}\
|
|
731
|
+ of ${timeout} ms`);
|
671
|
732
|
this.clearTimeout(participantId);
|
672
|
733
|
this.figureOutConnectionStatus(participantId);
|
673
|
|
- }, this.rtcMuteTimeout);
|
|
734
|
+ }, timeout);
|
674
|
735
|
}
|
675
|
736
|
}
|
676
|
737
|
|
|
@@ -683,7 +744,8 @@ export default class ParticipantConnectionStatusHandler {
|
683
|
744
|
onTrackRtcUnmuted(track) {
|
684
|
745
|
const participantId = track.getParticipantId();
|
685
|
746
|
|
686
|
|
- logger.debug(`Detector track RTC unmuted: ${participantId}`);
|
|
747
|
+ logger.debug(
|
|
748
|
+ `Detector track RTC unmuted: ${participantId}`, Date.now());
|
687
|
749
|
|
688
|
750
|
this.clearTimeout(participantId);
|
689
|
751
|
this.clearRtcMutedTimestamp(participantId);
|