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
     this.eventEmitter = new EventEmitter();
76
     this.eventEmitter = new EventEmitter();
77
     this.options = options;
77
     this.options = options;
78
     this.eventManager = new JitsiConferenceEventManager(this);
78
     this.eventManager = new JitsiConferenceEventManager(this);
79
+    this.participants = {};
79
     this._init(options);
80
     this._init(options);
80
     this.componentsVersions = new ComponentsVersions(this);
81
     this.componentsVersions = new ComponentsVersions(this);
81
-    this.participants = {};
82
 
82
 
83
     /**
83
     /**
84
      * Jingle session instance for the JVB connection.
84
      * Jingle session instance for the JVB connection.
220
 
220
 
221
     this.participantConnectionStatus
221
     this.participantConnectionStatus
222
         = new ParticipantConnectionStatusHandler(
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
     this.participantConnectionStatus.init();
234
     this.participantConnectionStatus.init();
226
 
235
 
227
     if (!this.statistics) {
236
     if (!this.statistics) {
950
  * like to check.
959
  * like to check.
951
  * @return {boolean} true if the participant with id is in the last N set or
960
  * @return {boolean} true if the participant with id is in the last N set or
952
  * if there's no last N set, false otherwise.
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
 JitsiConference.prototype.isInLastN = function(participantId) {
965
 JitsiConference.prototype.isInLastN = function(participantId) {
955
     return this.rtc.isInLastN(participantId);
966
     return this.rtc.isInLastN(participantId);

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

96
          * @private
96
          * @private
97
          * @type {number}
97
          * @type {number}
98
          */
98
          */
99
-        this._lastN = null;
99
+        this._lastN = -1;
100
 
100
 
101
         /**
101
         /**
102
          * Defines the last N endpoints list. It can be null or an array once
102
          * Defines the last N endpoints list. It can be null or an array once
203
 
203
 
204
                 // If setLastN was invoked before the data channels completed
204
                 // If setLastN was invoked before the data channels completed
205
                 // opening, apply the specified value now that the data channels
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
             this.addListener(RTCEvents.DATA_CHANNEL_OPEN,
212
             this.addListener(RTCEvents.DATA_CHANNEL_OPEN,
402
         track.conference = this.conference;
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
      * Get local video track.
415
      * Get local video track.
407
      * @returns {JitsiLocalTrack|undefined}
416
      * @returns {JitsiLocalTrack|undefined}
717
      * @param value {number} the new value for lastN.
726
      * @param value {number} the new value for lastN.
718
      */
727
      */
719
     setLastN(value) {
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
             this._lastN = value;
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
 
9
 
10
 const logger = getLogger(__filename);
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
  * Default value of 2000 milliseconds for
21
  * Default value of 2000 milliseconds for
14
  * {@link ParticipantConnectionStatus.rtcMuteTimeout}.
22
  * {@link ParticipantConnectionStatus.rtcMuteTimeout}.
160
      * @constructor
168
      * @constructor
161
      * @param {RTC} rtc the RTC service instance
169
      * @param {RTC} rtc the RTC service instance
162
      * @param {JitsiConference} conference parent conference instance
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
      * {@link ParticipantConnectionStatus.rtcMuteTimeout}.
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
         this.rtc = rtc;
178
         this.rtc = rtc;
168
         this.conference = conference;
179
         this.conference = conference;
169
 
180
 
183
          */
194
          */
184
         this.connStatusFromJvb = { };
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
          * How long we're going to wait after the RTC video track muted event
213
          * How long we're going to wait after the RTC video track muted event
188
          * for the corresponding signalling mute event, before the connection
214
          * for the corresponding signalling mute event, before the connection
192
          * @type {number} amount of time in milliseconds
218
          * @type {number} amount of time in milliseconds
193
          */
219
          */
194
         this.rtcMuteTimeout
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
          * This map holds a timestamp indicating  when participant's video track
225
          * This map holds a timestamp indicating  when participant's video track
241
         this.restoringTimers = new Map();
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
      * Initializes <tt>ParticipantConnectionStatus</tt> and bind required event
283
      * Initializes <tt>ParticipantConnectionStatus</tt> and bind required event
246
      * listeners.
284
      * listeners.
293
         this.conference.on(
331
         this.conference.on(
294
             JitsiConferenceEvents.LAST_N_ENDPOINTS_CHANGED,
332
             JitsiConferenceEvents.LAST_N_ENDPOINTS_CHANGED,
295
             this._onLastNChanged);
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
             JitsiConferenceEvents.LAST_N_ENDPOINTS_CHANGED,
368
             JitsiConferenceEvents.LAST_N_ENDPOINTS_CHANGED,
326
             this._onLastNChanged);
369
             this._onLastNChanged);
327
 
370
 
371
+        this.rtc.removeListener(
372
+            RTCEvents.LASTN_VALUE_CHANGED, this._onLastNValueChanged);
373
+
328
         this.conference.off(
374
         this.conference.off(
329
             JitsiConferenceEvents.P2P_STATUS, this._onP2PStatus);
375
             JitsiConferenceEvents.P2P_STATUS, this._onP2PStatus);
330
 
376
 
479
             return false;
525
             return false;
480
         }
526
         }
481
 
527
 
528
+        const id = participant.getId();
482
         const hasAnyVideoRTCMuted = participant.hasAnyVideoTrackWebRTCMuted();
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
         return hasAnyVideoRTCMuted
533
         return hasAnyVideoRTCMuted
487
             && typeof rtcMutedTimestamp === 'number'
534
             && typeof rtcMutedTimestamp === 'number'
488
-            && (Date.now() - rtcMutedTimestamp) >= this.rtcMuteTimeout;
535
+            && (Date.now() - rtcMutedTimestamp) >= timeout;
489
     }
536
     }
490
 
537
 
491
     /**
538
     /**
525
 
572
 
526
         const inP2PMode = this.conference.isP2PActive();
573
         const inP2PMode = this.conference.isP2PActive();
527
         const isRestoringTimedOut = this._isRestoringTimedout(id);
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
         const isVideoTrackFrozen = this.isVideoTrackFrozen(participant);
580
         const isVideoTrackFrozen = this.isVideoTrackFrozen(participant);
530
         const isInLastN = this.rtc.isInLastN(id);
581
         const isInLastN = this.rtc.isInLastN(id);
531
         let isConnActiveByJvb = this.connStatusFromJvb[id];
582
         let isConnActiveByJvb = this.connStatusFromJvb[id];
576
      * @private
627
      * @private
577
      */
628
      */
578
     _onLastNChanged(leavingLastN = [], enteringLastN = []) {
629
     _onLastNChanged(leavingLastN = [], enteringLastN = []) {
630
+        const now = Date.now();
631
+
632
+        logger.debug(
633
+            'leaving/entering lastN', leavingLastN, enteringLastN, now);
634
+
579
         for (const id of leavingLastN) {
635
         for (const id of leavingLastN) {
580
             this.enteredLastNTimestamp.delete(id);
636
             this.enteredLastNTimestamp.delete(id);
581
             this._clearRestoringTimer(id);
637
             this._clearRestoringTimer(id);
583
         }
639
         }
584
         for (const id of enteringLastN) {
640
         for (const id of enteringLastN) {
585
             // store the timestamp this id is entering lastN
641
             // store the timestamp this id is entering lastN
586
-            this.enteredLastNTimestamp.set(id, Date.now());
587
-
642
+            this.enteredLastNTimestamp.set(id, now);
588
             this.figureOutConnectionStatus(id);
643
             this.figureOutConnectionStatus(id);
589
         }
644
         }
590
     }
645
     }
654
         const participantId = track.getParticipantId();
709
         const participantId = track.getParticipantId();
655
         const participant = this.conference.getParticipantById(participantId);
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
         if (!participant) {
713
         if (!participant) {
659
             logger.error(`No participant for id: ${participantId}`);
714
             logger.error(`No participant for id: ${participantId}`);
660
 
715
 
666
             // it some time, before the connection interrupted event is
721
             // it some time, before the connection interrupted event is
667
             // triggered.
722
             // triggered.
668
             this.clearTimeout(participantId);
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
             this.trackTimers[participantId] = window.setTimeout(() => {
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
                 this.clearTimeout(participantId);
732
                 this.clearTimeout(participantId);
672
                 this.figureOutConnectionStatus(participantId);
733
                 this.figureOutConnectionStatus(participantId);
673
-            }, this.rtcMuteTimeout);
734
+            }, timeout);
674
         }
735
         }
675
     }
736
     }
676
 
737
 
683
     onTrackRtcUnmuted(track) {
744
     onTrackRtcUnmuted(track) {
684
         const participantId = track.getParticipantId();
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
         this.clearTimeout(participantId);
750
         this.clearTimeout(participantId);
689
         this.clearRtcMutedTimestamp(participantId);
751
         this.clearRtcMutedTimestamp(participantId);

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

14
     ENDPOINT_CONN_STATUS_CHANGED: 'rtc.endpoint_conn_status_changed',
14
     ENDPOINT_CONN_STATUS_CHANGED: 'rtc.endpoint_conn_status_changed',
15
     DOMINANT_SPEAKER_CHANGED: 'rtc.dominant_speaker_changed',
15
     DOMINANT_SPEAKER_CHANGED: 'rtc.dominant_speaker_changed',
16
     LASTN_ENDPOINT_CHANGED: 'rtc.lastn_endpoint_changed',
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
     AVAILABLE_DEVICES_CHANGED: 'rtc.available_devices_changed',
24
     AVAILABLE_DEVICES_CHANGED: 'rtc.available_devices_changed',
18
     TRACK_ATTACHED: 'rtc.track_attached',
25
     TRACK_ATTACHED: 'rtc.track_attached',
19
 
26
 

Loading…
Cancel
Save