Quellcode durchsuchen

Merge pull request #313 from jitsi/conn_problems_when_video_muted

Connection problems indication stalled when video muted
dev1
hristoterezov vor 9 Jahren
Ursprung
Commit
ce758f88e0
2 geänderte Dateien mit 113 neuen und 106 gelöschten Zeilen
  1. 109
    106
      modules/connectivity/ParticipantConnectionStatus.js
  2. 4
    0
      service/RTC/RTCEvents.js

+ 109
- 106
modules/connectivity/ParticipantConnectionStatus.js Datei anzeigen

@@ -46,7 +46,7 @@ export default class ParticipantConnectionStatus {
46 46
          * Required for getting back in sync when remote video track is removed.
47 47
          * @type {Object.<string, boolean>}
48 48
          */
49
-        this.rtcConnStatusCache = { };
49
+        this.connStatusFromJvb = { };
50 50
         /**
51 51
          * How long we're going to wait after the RTC video track muted event
52 52
          * for the corresponding signalling mute event, before the connection
@@ -58,6 +58,28 @@ export default class ParticipantConnectionStatus {
58 58
         this.rtcMuteTimeout
59 59
             = typeof rtcMuteTimeout === 'number'
60 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 83
         logger.info("RtcMuteTimeout set to: " + this.rtcMuteTimeout);
62 84
     }
63 85
 
@@ -134,10 +156,11 @@ export default class ParticipantConnectionStatus {
134 156
 
135 157
         Object.keys(this.trackTimers).forEach(function (participantId) {
136 158
             this.clearTimeout(participantId);
159
+            this.clearRtcMutedTimestamp(participantId);
137 160
         }.bind(this));
138 161
 
139 162
         // Clear RTC connection status cache
140
-        this.rtcConnStatusCache = {};
163
+        this.connStatusFromJvb = {};
141 164
     }
142 165
 
143 166
     /**
@@ -155,42 +178,17 @@ export default class ParticipantConnectionStatus {
155 178
 
156 179
         // Filter out events for the local JID for now
157 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 188
         if (participant.isConnectionActive() !== newStatus) {
193 189
 
190
+            const endpointId = participant.getId();
191
+
194 192
             participant._setIsConnectionActive(newStatus);
195 193
 
196 194
             logger.debug(
@@ -229,6 +227,16 @@ export default class ParticipantConnectionStatus {
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 241
      * Bind signalling mute event listeners for video {JitsiRemoteTrack} when
234 242
      * a new one is added to the conference.
@@ -271,32 +279,48 @@ export default class ParticipantConnectionStatus {
271 279
                 this._onSignallingMuteChanged);
272 280
 
273 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 324
         if (!participant) {
301 325
             // Probably the participant is no longer in the conference
302 326
             // (at the time of writing this code, participant is
@@ -304,37 +328,31 @@ export default class ParticipantConnectionStatus {
304 328
             // fired),
305 329
             // so we don't care, but let's print the warning for
306 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 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 339
         // If no status was received from the JVB it means that it's active
318 340
         // (the bridge does not send notification unless there is a problem).
319 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 343
             isConnActiveByJvb = true;
322 344
         }
323 345
 
346
+        let isConnectionActive
347
+            = isConnActiveByJvb && (isVideoMuted || !isVideoTrackFrozen);
348
+
324 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,25 +362,23 @@ export default class ParticipantConnectionStatus {
344 362
      * will be processed.
345 363
      */
346 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 367
         logger.debug('Detector track RTC muted: ' + participantId);
350 368
         if (!participant) {
351 369
             logger.error('No participant for id: ' + participantId);
352 370
             return;
353 371
         }
372
+        this.rtcMutedTimestamp[participantId] = Date.now();
354 373
         if (!participant.isVideoMuted()) {
355 374
             // If the user is not muted according to the signalling we'll give
356 375
             // it some time, before the connection interrupted event is
357 376
             // triggered.
377
+            this.clearTimeout(participantId);
358 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 380
                 this.clearTimeout(participantId);
381
+                this.figureOutConnectionStatus(participantId);
366 382
             }.bind(this), this.rtcMuteTimeout);
367 383
         }
368 384
     }
@@ -374,17 +390,14 @@ export default class ParticipantConnectionStatus {
374 390
      * event will be processed.
375 391
      */
376 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 397
         this.clearTimeout(participantId);
398
+        this.clearRtcMutedTimestamp(participantId);
399
+
400
+        this.figureOutConnectionStatus(participantId);
388 401
     }
389 402
 
390 403
     /**
@@ -394,23 +407,13 @@ export default class ParticipantConnectionStatus {
394 407
      * the signalling mute/unmute event will be processed.
395 408
      */
396 409
     onSignallingMuteChanged (track) {
410
+        const participantId = track.getParticipantId();
411
+
397 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 Datei anzeigen

@@ -7,7 +7,11 @@ var RTCEvents = {
7 7
     LASTN_ENDPOINT_CHANGED: "rtc.lastn_endpoint_changed",
8 8
     AVAILABLE_DEVICES_CHANGED: "rtc.available_devices_changed",
9 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 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 15
     REMOTE_TRACK_UNMUTE: "rtc.remote_track_unmute",
12 16
     AUDIO_OUTPUT_DEVICE_CHANGED: "rtc.audio_output_device_changed",
13 17
     DEVICE_LIST_CHANGED: "rtc.device_list_changed",

Laden…
Abbrechen
Speichern