浏览代码

Merge pull request #313 from jitsi/conn_problems_when_video_muted

Connection problems indication stalled when video muted
dev1
hristoterezov 9 年前
父节点
当前提交
ce758f88e0
共有 2 个文件被更改,包括 113 次插入106 次删除
  1. 109
    106
      modules/connectivity/ParticipantConnectionStatus.js
  2. 4
    0
      service/RTC/RTCEvents.js

+ 109
- 106
modules/connectivity/ParticipantConnectionStatus.js 查看文件

46
          * Required for getting back in sync when remote video track is removed.
46
          * Required for getting back in sync when remote video track is removed.
47
          * @type {Object.<string, boolean>}
47
          * @type {Object.<string, boolean>}
48
          */
48
          */
49
-        this.rtcConnStatusCache = { };
49
+        this.connStatusFromJvb = { };
50
         /**
50
         /**
51
          * How long we're going to wait after the RTC video track muted event
51
          * How long we're going to wait after the RTC video track muted event
52
          * for the corresponding signalling mute event, before the connection
52
          * for the corresponding signalling mute event, before the connection
58
         this.rtcMuteTimeout
58
         this.rtcMuteTimeout
59
             = typeof rtcMuteTimeout === 'number'
59
             = typeof rtcMuteTimeout === 'number'
60
                 ? rtcMuteTimeout : DEFAULT_RTC_MUTE_TIMEOUT;
60
                 ? rtcMuteTimeout : DEFAULT_RTC_MUTE_TIMEOUT;
61
+        /**
62
+         * This map holds a timestamp indicating  when participant's video track
63
+         * was RTC muted (it is assumed that each participant can have only 1
64
+         * video track at a time). The purpose of storing the timestamp is to
65
+         * avoid the transition to disconnected status in case of legitimate
66
+         * video mute operation where the signalling video muted event can
67
+         * arrive shortly after RTC muted event.
68
+         *
69
+         * The key is participant's ID which is the same as endpoint id in
70
+         * the Colibri conference allocated on the JVB.
71
+         *
72
+         * The value is a timestamp measured in milliseconds obtained with
73
+         * <tt>Date.now()</tt>.
74
+         *
75
+         * FIXME merge this logic with NO_DATA_FROM_SOURCE event
76
+         *       implemented in JitsiLocalTrack by extending the event to
77
+         *       the remote track and allowing to set different timeout for
78
+         *       local and remote tracks.
79
+         *
80
+         * @type {Object.<string, number>}
81
+         */
82
+        this.rtcMutedTimestamp = { };
61
         logger.info("RtcMuteTimeout set to: " + this.rtcMuteTimeout);
83
         logger.info("RtcMuteTimeout set to: " + this.rtcMuteTimeout);
62
     }
84
     }
63
 
85
 
134
 
156
 
135
         Object.keys(this.trackTimers).forEach(function (participantId) {
157
         Object.keys(this.trackTimers).forEach(function (participantId) {
136
             this.clearTimeout(participantId);
158
             this.clearTimeout(participantId);
159
+            this.clearRtcMutedTimestamp(participantId);
137
         }.bind(this));
160
         }.bind(this));
138
 
161
 
139
         // Clear RTC connection status cache
162
         // Clear RTC connection status cache
140
-        this.rtcConnStatusCache = {};
163
+        this.connStatusFromJvb = {};
141
     }
164
     }
142
 
165
 
143
     /**
166
     /**
155
 
178
 
156
         // Filter out events for the local JID for now
179
         // Filter out events for the local JID for now
157
         if (endpointId !== this.conference.myUserId()) {
180
         if (endpointId !== this.conference.myUserId()) {
158
-
159
-            // Cache the status received received over the data channels, as
160
-            // it will be needed to verify for out of sync when the remote video
161
-            // track is being removed.
162
-            this.rtcConnStatusCache[endpointId] = isActive;
163
-
164
-            var participant = this.conference.getParticipantById(endpointId);
165
-            // Delay the 'active' event until the video track gets
166
-            // the RTC unmuted event
167
-            if (isActive
168
-                    && RTCBrowserType.isVideoMuteOnConnInterruptedSupported()
169
-                    && participant
170
-                    && participant.hasAnyVideoTrackWebRTCMuted()
171
-                    && !participant.isVideoMuted()) {
172
-                logger.debug(
173
-                    'Ignoring RTCEvents.ENDPOINT_CONN_STATUS_CHANGED -'
174
-                        + ' will wait for unmute event');
175
-            } else {
176
-                this._changeConnectionStatus(endpointId, isActive);
177
-            }
181
+            // Store the status received over the data channels
182
+            this.connStatusFromJvb[endpointId] = isActive;
183
+            this.figureOutConnectionStatus(endpointId);
178
         }
184
         }
179
     }
185
     }
180
 
186
 
181
-    _changeConnectionStatus(endpointId, newStatus) {
182
-        var participant = this.conference.getParticipantById(endpointId);
183
-        if (!participant) {
184
-            // This will happen when participant exits the conference with
185
-            // broken ICE connection and we join after that. The bridge keeps
186
-            // sending that notification until the conference does not expire.
187
-            logger.warn(
188
-                'Missed participant connection status update - ' +
189
-                    'no participant for endpoint: ' + endpointId);
190
-            return;
191
-        }
187
+    _changeConnectionStatus(participant, newStatus) {
192
         if (participant.isConnectionActive() !== newStatus) {
188
         if (participant.isConnectionActive() !== newStatus) {
193
 
189
 
190
+            const endpointId = participant.getId();
191
+
194
             participant._setIsConnectionActive(newStatus);
192
             participant._setIsConnectionActive(newStatus);
195
 
193
 
196
             logger.debug(
194
             logger.debug(
229
         }
227
         }
230
     }
228
     }
231
 
229
 
230
+    /**
231
+     * Clears the timestamp of the RTC muted event for participant's video track
232
+     * @param participantId the id of the conference participant which is
233
+     * the same as the Colibri endpoint ID of the video channel allocated for
234
+     * the user on the videobridge.
235
+     */
236
+    clearRtcMutedTimestamp(participantId) {
237
+        this.rtcMutedTimestamp[participantId] = null;
238
+    }
239
+
232
     /**
240
     /**
233
      * Bind signalling mute event listeners for video {JitsiRemoteTrack} when
241
      * Bind signalling mute event listeners for video {JitsiRemoteTrack} when
234
      * a new one is added to the conference.
242
      * a new one is added to the conference.
271
                 this._onSignallingMuteChanged);
279
                 this._onSignallingMuteChanged);
272
 
280
 
273
             this.clearTimeout(endpointId);
281
             this.clearTimeout(endpointId);
282
+            this.clearRtcMutedTimestamp(endpointId);
274
 
283
 
275
-            // Only if we're using video muted events - check if the JVB status
276
-            // should be restored from cache.
277
-            if (RTCBrowserType.isVideoMuteOnConnInterruptedSupported())
278
-            {
279
-                this.maybeRestoreCachedStatus(endpointId);
280
-            }
284
+            this.figureOutConnectionStatus(endpointId);
285
+        }
286
+    }
287
+
288
+    /**
289
+     * Checks if given participant's video is considered frozen.
290
+     * @param {JitsiParticipant} participant
291
+     * @return {boolean} <tt>true</tt> if the video has frozen for given
292
+     * participant or <tt>false</tt> when it's either not considered frozen
293
+     * (yet) or if freeze detection is not supported by the current browser.
294
+     *
295
+     * FIXME merge this logic with NO_DATA_FROM_SOURCE event
296
+     *       implemented in JitsiLocalTrack by extending the event to
297
+     *       the remote track and allowing to set different timeout for
298
+     *       local and remote tracks.
299
+     *
300
+     */
301
+    isVideoTrackFrozen (participant) {
302
+        if (!RTCBrowserType.isVideoMuteOnConnInterruptedSupported()) {
303
+            return false;
281
         }
304
         }
305
+
306
+        const hasAnyVideoRTCMuted = participant.hasAnyVideoTrackWebRTCMuted();
307
+        const rtcMutedTimestamp
308
+            = this.rtcMutedTimestamp[participant.getId()];
309
+
310
+        return hasAnyVideoRTCMuted
311
+            && typeof rtcMutedTimestamp === 'number'
312
+            && (Date.now() - rtcMutedTimestamp) >= this.rtcMuteTimeout;
282
     }
313
     }
283
 
314
 
284
     /**
315
     /**
285
-     * When RTC video track muted events are taken into account,
286
-     * at the point when the track is being removed we have to update
287
-     * to the current connectivity status according to the JVB. That's
288
-     * because if the current track is muted then the new one which
289
-     * replaces it is always added as unmuted and there may be no
290
-     * 'muted'/'unmuted' event sequence if the connection restores in
291
-     * the meantime.
316
+     * Figures out (and updates) the current connectivity status for
317
+     * the participant identified by the given id.
292
      *
318
      *
293
-     * XXX See onEndpointConnStatusChanged method where the update is
294
-     * postponed and which is the cause for this workaround. If we
295
-     * decide to not wait for video unmuted event and accept the JVB
296
-     * status immediately then it's fine to remove the code below.
319
+     * @param {string} id the participant's id (MUC nickname or Colibri endpoint
320
+     * ID).
297
      */
321
      */
298
-    maybeRestoreCachedStatus(endpointId) {
299
-        var participant = this.conference.getParticipantById(endpointId);
322
+    figureOutConnectionStatus(id) {
323
+        const participant = this.conference.getParticipantById(id);
300
         if (!participant) {
324
         if (!participant) {
301
             // Probably the participant is no longer in the conference
325
             // Probably the participant is no longer in the conference
302
             // (at the time of writing this code, participant is
326
             // (at the time of writing this code, participant is
304
             // fired),
328
             // fired),
305
             // so we don't care, but let's print the warning for
329
             // so we don't care, but let's print the warning for
306
             // debugging purpose
330
             // debugging purpose
307
-            logger.warn(
308
-                'maybeRestoreCachedStatus - ' +
309
-                'no participant for endpoint: ' + endpointId);
331
+            logger.warn('figure out conn status - no participant for: ' + id);
310
             return;
332
             return;
311
         }
333
         }
312
 
334
 
313
-        const isConnectionActive = participant.isConnectionActive();
314
-        const hasAnyVideoRTCMuted = participant.hasAnyVideoTrackWebRTCMuted();
315
-        let isConnActiveByJvb = this.rtcConnStatusCache[endpointId];
335
+        const isVideoMuted = participant.isVideoMuted();
336
+        const isVideoTrackFrozen = this.isVideoTrackFrozen(participant);
337
+        let isConnActiveByJvb = this.connStatusFromJvb[id];
316
 
338
 
317
         // If no status was received from the JVB it means that it's active
339
         // If no status was received from the JVB it means that it's active
318
         // (the bridge does not send notification unless there is a problem).
340
         // (the bridge does not send notification unless there is a problem).
319
         if (typeof isConnActiveByJvb !== 'boolean') {
341
         if (typeof isConnActiveByJvb !== 'boolean') {
320
-            logger.debug("Assuming connection active by JVB - no notification");
342
+            logger.debug('Assuming connection active by JVB - no notification');
321
             isConnActiveByJvb = true;
343
             isConnActiveByJvb = true;
322
         }
344
         }
323
 
345
 
346
+        let isConnectionActive
347
+            = isConnActiveByJvb && (isVideoMuted || !isVideoTrackFrozen);
348
+
324
         logger.debug(
349
         logger.debug(
325
-            "Remote track removed, is active: " + isConnectionActive
326
-            + " is active(jvb):" + isConnActiveByJvb
327
-            + " video RTC muted:" + hasAnyVideoRTCMuted);
328
-
329
-        if (!isConnectionActive && isConnActiveByJvb && !hasAnyVideoRTCMuted) {
330
-            // FIXME adjust the log level or remove the message completely once
331
-            // the feature gets mature enough.
332
-            logger.info(
333
-                "Remote track removed for disconnected" +
334
-                " participant, when the status according to" +
335
-                " the JVB is connected. Adjusting to the JVB value.");
336
-            this._changeConnectionStatus(endpointId, isConnActiveByJvb);
337
-        }
350
+            'Figure out conn status, is video muted: ' + isVideoMuted
351
+                + ' is active(jvb): ' + isConnActiveByJvb
352
+                + ' video track frozen: ' + isVideoTrackFrozen
353
+                + ' => ' + isConnectionActive);
354
+
355
+        this._changeConnectionStatus(participant, isConnectionActive);
338
     }
356
     }
339
 
357
 
340
     /**
358
     /**
344
      * will be processed.
362
      * will be processed.
345
      */
363
      */
346
     onTrackRtcMuted(track) {
364
     onTrackRtcMuted(track) {
347
-        var participantId = track.getParticipantId();
348
-        var participant = this.conference.getParticipantById(participantId);
365
+        const participantId = track.getParticipantId();
366
+        const participant = this.conference.getParticipantById(participantId);
349
         logger.debug('Detector track RTC muted: ' + participantId);
367
         logger.debug('Detector track RTC muted: ' + participantId);
350
         if (!participant) {
368
         if (!participant) {
351
             logger.error('No participant for id: ' + participantId);
369
             logger.error('No participant for id: ' + participantId);
352
             return;
370
             return;
353
         }
371
         }
372
+        this.rtcMutedTimestamp[participantId] = Date.now();
354
         if (!participant.isVideoMuted()) {
373
         if (!participant.isVideoMuted()) {
355
             // If the user is not muted according to the signalling we'll give
374
             // If the user is not muted according to the signalling we'll give
356
             // it some time, before the connection interrupted event is
375
             // it some time, before the connection interrupted event is
357
             // triggered.
376
             // triggered.
377
+            this.clearTimeout(participantId);
358
             this.trackTimers[participantId] = window.setTimeout(function () {
378
             this.trackTimers[participantId] = window.setTimeout(function () {
359
-                if (!track.isMuted() && participant.isConnectionActive()) {
360
-                    logger.info(
361
-                        'Connection interrupted through the RTC mute: '
362
-                            + participantId, Date.now());
363
-                    this._changeConnectionStatus(participantId, false);
364
-                }
379
+                logger.debug('RTC mute timeout for: ' + participantId);
365
                 this.clearTimeout(participantId);
380
                 this.clearTimeout(participantId);
381
+                this.figureOutConnectionStatus(participantId);
366
             }.bind(this), this.rtcMuteTimeout);
382
             }.bind(this), this.rtcMuteTimeout);
367
         }
383
         }
368
     }
384
     }
374
      * event will be processed.
390
      * event will be processed.
375
      */
391
      */
376
     onTrackRtcUnmuted(track) {
392
     onTrackRtcUnmuted(track) {
377
-        logger.debug('Detector track RTC unmuted: ', track);
378
-        var participantId = track.getParticipantId();
379
-        if (!track.isMuted() &&
380
-            !this.conference.getParticipantById(participantId)
381
-                .isConnectionActive()) {
382
-            logger.info(
383
-                'Detector connection restored through the RTC unmute: '
384
-                    + participantId, Date.now());
385
-            this._changeConnectionStatus(participantId, true);
386
-        }
393
+        const participantId = track.getParticipantId();
394
+
395
+        logger.debug('Detector track RTC unmuted: ' + participantId);
396
+
387
         this.clearTimeout(participantId);
397
         this.clearTimeout(participantId);
398
+        this.clearRtcMutedTimestamp(participantId);
399
+
400
+        this.figureOutConnectionStatus(participantId);
388
     }
401
     }
389
 
402
 
390
     /**
403
     /**
394
      * the signalling mute/unmute event will be processed.
407
      * the signalling mute/unmute event will be processed.
395
      */
408
      */
396
     onSignallingMuteChanged (track) {
409
     onSignallingMuteChanged (track) {
410
+        const participantId = track.getParticipantId();
411
+
397
         logger.debug(
412
         logger.debug(
398
-            'Detector on track signalling mute changed: ',
399
-            track, track.isMuted());
400
-        var isMuted = track.isMuted();
401
-        var participantId = track.getParticipantId();
402
-        var participant = this.conference.getParticipantById(participantId);
403
-        if (!participant) {
404
-            logger.error('No participant for id: ' + participantId);
405
-            return;
406
-        }
407
-        var isConnectionActive = participant.isConnectionActive();
408
-        if (isMuted && isConnectionActive && this.trackTimers[participantId]) {
409
-            logger.debug(
410
-                'Signalling got in sync - cancelling task for: '
411
-                    + participantId);
412
-            this.clearTimeout(participantId);
413
-        }
413
+            'Detector on track signalling mute changed: '
414
+                + participantId, track.isMuted());
415
+
416
+        this.figureOutConnectionStatus(participantId);
414
     }
417
     }
415
 
418
 
416
 }
419
 }

+ 4
- 0
service/RTC/RTCEvents.js 查看文件

7
     LASTN_ENDPOINT_CHANGED: "rtc.lastn_endpoint_changed",
7
     LASTN_ENDPOINT_CHANGED: "rtc.lastn_endpoint_changed",
8
     AVAILABLE_DEVICES_CHANGED: "rtc.available_devices_changed",
8
     AVAILABLE_DEVICES_CHANGED: "rtc.available_devices_changed",
9
     TRACK_ATTACHED: "rtc.track_attached",
9
     TRACK_ATTACHED: "rtc.track_attached",
10
+    // FIXME get rid of this event in favour of NO_DATA_FROM_SOURCE event
11
+    // (currently implemented for local tracks only)
10
     REMOTE_TRACK_MUTE: "rtc.remote_track_mute",
12
     REMOTE_TRACK_MUTE: "rtc.remote_track_mute",
13
+    // FIXME get rid of this event in favour of NO_DATA_FROM_SOURCE event
14
+    // (currently implemented for local tracks only)
11
     REMOTE_TRACK_UNMUTE: "rtc.remote_track_unmute",
15
     REMOTE_TRACK_UNMUTE: "rtc.remote_track_unmute",
12
     AUDIO_OUTPUT_DEVICE_CHANGED: "rtc.audio_output_device_changed",
16
     AUDIO_OUTPUT_DEVICE_CHANGED: "rtc.audio_output_device_changed",
13
     DEVICE_LIST_CHANGED: "rtc.device_list_changed",
17
     DEVICE_LIST_CHANGED: "rtc.device_list_changed",

正在加载...
取消
保存