Browse Source

Last n improvements (#493)

* doc(JitsiConference): deprecate 'isInLastN'

The 'isInLastN' method should not be used for the UI purposes, but
ParticipantConnectionStatus value should be used instead.

* fix(ParticipantConn..Status): speed up INACTIVE transition

Before this change when user's video stops playing, after user is
removed from last N we were waiting 2 seconds, before going to INACTIVE
state. This commit reduces the time to 500ms for such case.

* fix(ParticipantConn...): reduce logging

Reduce logging verbosity.

* fix(ParticipantConn...): handle LastN == 0

When LastN is set to 0 we should not rely on video playback and last N
set for figuring out participant connection status.

* fix(JitsiConference): undefined participants

Fixes a crash when this.participants field is accessed from _init.
dev1
Paweł Domas 8 years ago
parent
commit
c7565628a0
4 changed files with 116 additions and 28 deletions
  1. 14
    3
      JitsiConference.js
  2. 19
    11
      modules/RTC/RTC.js
  3. 76
    14
      modules/connectivity/ParticipantConnectionStatus.js
  4. 7
    0
      service/RTC/RTCEvents.js

+ 14
- 3
JitsiConference.js View File

@@ -76,9 +76,9 @@ export default function JitsiConference(options) {
76 76
     this.eventEmitter = new EventEmitter();
77 77
     this.options = options;
78 78
     this.eventManager = new JitsiConferenceEventManager(this);
79
+    this.participants = {};
79 80
     this._init(options);
80 81
     this.componentsVersions = new ComponentsVersions(this);
81
-    this.participants = {};
82 82
 
83 83
     /**
84 84
      * Jingle session instance for the JVB connection.
@@ -220,8 +220,17 @@ JitsiConference.prototype._init = function(options = {}) {
220 220
 
221 221
     this.participantConnectionStatus
222 222
         = new ParticipantConnectionStatusHandler(
223
-                this.rtc, this,
224
-                options.config.peerDisconnectedThroughRtcTimeout);
223
+            this.rtc,
224
+            this,
225
+            {   // Both these options are not public API, leaving it here only
226
+                // as an entry point through config for tuning up purposes.
227
+                // Default values should be adjusted as soon as optimal values
228
+                // are discovered.
229
+                rtcMuteTimeout:
230
+                    this.options.config._peerConnStatusRtcMuteTimeout,
231
+                outOfLastNTimeout:
232
+                    this.options.config._peerConnStatusOutOfLastNTimeout
233
+            });
225 234
     this.participantConnectionStatus.init();
226 235
 
227 236
     if (!this.statistics) {
@@ -950,6 +959,8 @@ JitsiConference.prototype.setLastN = function(lastN) {
950 959
  * like to check.
951 960
  * @return {boolean} true if the participant with id is in the last N set or
952 961
  * if there's no last N set, false otherwise.
962
+ * @deprecated this method should never be used to figure out the UI, but
963
+ * {@link ParticipantConnectionStatus} should be used instead.
953 964
  */
954 965
 JitsiConference.prototype.isInLastN = function(participantId) {
955 966
     return this.rtc.isInLastN(participantId);

+ 19
- 11
modules/RTC/RTC.js View File

@@ -96,7 +96,7 @@ export default class RTC extends Listenable {
96 96
          * @private
97 97
          * @type {number}
98 98
          */
99
-        this._lastN = null;
99
+        this._lastN = -1;
100 100
 
101 101
         /**
102 102
          * Defines the last N endpoints list. It can be null or an array once
@@ -203,10 +203,10 @@ export default class RTC extends Listenable {
203 203
 
204 204
                 // If setLastN was invoked before the data channels completed
205 205
                 // opening, apply the specified value now that the data channels
206
-                // are open.
207
-                if (this._lastN !== null) {
208
-                    this.setLastN(this._lastN);
209
-                    this._lastN = null;
206
+                // are open. NOTE that -1 is the default value assumed by both
207
+                // RTC module and the JVB.
208
+                if (this._lastN !== -1) {
209
+                    this.dataChannels.sendSetLastNMessage(this._lastN);
210 210
                 }
211 211
             };
212 212
             this.addListener(RTCEvents.DATA_CHANNEL_OPEN,
@@ -402,6 +402,15 @@ export default class RTC extends Listenable {
402 402
         track.conference = this.conference;
403 403
     }
404 404
 
405
+    /**
406
+     * Returns the current value for "lastN" - the amount of videos are going
407
+     * to be delivered. When set to -1 for unlimited or all available videos.
408
+     * @return {number}
409
+     */
410
+    getLastN() {
411
+        return this._lastN;
412
+    }
413
+
405 414
     /**
406 415
      * Get local video track.
407 416
      * @returns {JitsiLocalTrack|undefined}
@@ -717,13 +726,12 @@ export default class RTC extends Listenable {
717 726
      * @param value {number} the new value for lastN.
718 727
      */
719 728
     setLastN(value) {
720
-        if (this.dataChannels && this.dataChannelsOpen) {
721
-            this.dataChannels.sendSetLastNMessage(value);
722
-        } else {
723
-            // No data channel has been initialized or has completed opening
724
-            // yet. Remember the specified value and apply it as soon as a data
725
-            // channel opens.
729
+        if (this._lastN !== value) {
726 730
             this._lastN = value;
731
+            if (this.dataChannels && this.dataChannelsOpen) {
732
+                this.dataChannels.sendSetLastNMessage(value);
733
+            }
734
+            this.eventEmitter.emit(RTCEvents.LASTN_VALUE_CHANGED, value);
727 735
         }
728 736
     }
729 737
 

+ 76
- 14
modules/connectivity/ParticipantConnectionStatus.js View File

@@ -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);

+ 7
- 0
service/RTC/RTCEvents.js View File

@@ -14,6 +14,13 @@ const RTCEvents = {
14 14
     ENDPOINT_CONN_STATUS_CHANGED: 'rtc.endpoint_conn_status_changed',
15 15
     DOMINANT_SPEAKER_CHANGED: 'rtc.dominant_speaker_changed',
16 16
     LASTN_ENDPOINT_CHANGED: 'rtc.lastn_endpoint_changed',
17
+
18
+    /**
19
+     * Event emitted when {@link RTC.setLastN} method is called to update with
20
+     * the new value set.
21
+     * The first argument is the value passed to {@link RTC.setLastN}.
22
+     */
23
+    LASTN_VALUE_CHANGED: 'rtc.lastn_value_changed',
17 24
     AVAILABLE_DEVICES_CHANGED: 'rtc.available_devices_changed',
18 25
     TRACK_ATTACHED: 'rtc.track_attached',
19 26
 

Loading…
Cancel
Save