Pārlūkot izejas kodu

fix(RTC): Set transceiver direction after RTCRtpSender#replaceTrack.

This fixes the issue where TRACK_REMOVED event is not fired when a remote track is removed from the peerconnection.
Fixes https://github.com/jitsi/lib-jitsi-meet/issues/1612 and https://github.com/jitsi/jitsi-meet/issues/8482.
dev1
Jaya Allamsetty 4 gadus atpakaļ
vecāks
revīzija
4e5804f965

+ 0
- 18
modules/RTC/JitsiLocalTrack.js Parādīt failu

@@ -403,24 +403,6 @@ export default class JitsiLocalTrack extends JitsiTrack {
403 403
 
404 404
         this._setEffectInProgress = true;
405 405
 
406
-        if (browser.usesUnifiedPlan()) {
407
-            this._switchStreamEffect(effect);
408
-            if (this.isVideoTrack()) {
409
-                this.containers.forEach(cont => RTCUtils.attachMediaStream(cont, this.stream));
410
-            }
411
-
412
-            return conference.replaceTrack(this, this)
413
-                .then(() => {
414
-                    this._setEffectInProgress = false;
415
-                })
416
-                .catch(error => {
417
-                    this._setEffectInProgress = false;
418
-                    this._switchStreamEffect();
419
-                    logger.error('Failed to switch to the new stream!', error);
420
-                    throw error;
421
-                });
422
-        }
423
-
424 406
         // TODO: Create new JingleSessionPC method for replacing a stream in JitsiLocalTrack without offer answer.
425 407
         return conference.removeTrack(this)
426 408
             .then(() => {

+ 87
- 66
modules/RTC/TPCUtils.js Parādīt failu

@@ -10,6 +10,13 @@ const SIM_LAYER_1_RID = '1';
10 10
 const SIM_LAYER_2_RID = '2';
11 11
 const SIM_LAYER_3_RID = '3';
12 12
 
13
+const TransceiverDirection = {
14
+    INACTIVE: 'inactive',
15
+    RECVONLY: 'recvonly',
16
+    SENDONLY: 'sendonly',
17
+    SENDRECV: 'sendrecv'
18
+};
19
+
13 20
 export const SIM_LAYER_RIDS = [ SIM_LAYER_1_RID, SIM_LAYER_2_RID, SIM_LAYER_3_RID ];
14 21
 
15 22
 /**
@@ -63,6 +70,45 @@ export class TPCUtils {
63 70
         ];
64 71
     }
65 72
 
73
+    /**
74
+     * Returns the transceiver associated with a given RTCRtpSender/RTCRtpReceiver.
75
+     *
76
+     * @param {string} mediaType - type of track associated with the transceiver 'audio' or 'video'.
77
+     * @param {JitsiLocalTrack} localTrack - local track to be used for lookup.
78
+     * @returns {RTCRtpTransceiver}
79
+     */
80
+    _findTransceiver(mediaType, localTrack = null) {
81
+        let transceiver = null;
82
+
83
+        if (localTrack) {
84
+            transceiver = this.pc.peerconnection.getTransceivers()
85
+                .find(t => t.sender?.track?.id === localTrack.getTrackId());
86
+        } else if (mediaType) {
87
+            transceiver = this.pc.peerconnection.getTransceivers()
88
+                .find(t => t.receiver?.track?.kind === mediaType);
89
+        }
90
+
91
+        return transceiver;
92
+    }
93
+
94
+    /**
95
+     * Obtains stream encodings that need to be configured on the given track based
96
+     * on the track media type and the simulcast setting.
97
+     * @param {JitsiLocalTrack} localTrack
98
+     */
99
+    _getStreamEncodings(localTrack) {
100
+        if (this.pc.isSimulcastOn() && localTrack.isVideoTrack()) {
101
+            return this.localStreamEncodingsConfig;
102
+        }
103
+
104
+        return localTrack.isVideoTrack()
105
+            ? [ {
106
+                active: true,
107
+                maxBitrate: this.videoBitrates.high
108
+            } ]
109
+            : [ { active: true } ];
110
+    }
111
+
66 112
     /**
67 113
      * Ensures that the ssrcs associated with a FID ssrc-group appear in the correct order, i.e.,
68 114
      * the primary ssrc first and the secondary rtx ssrc later. This is important for unified
@@ -75,7 +121,7 @@ export class TPCUtils {
75 121
         const parsedSdp = transform.parse(description.sdp);
76 122
 
77 123
         parsedSdp.media.forEach(mLine => {
78
-            if (mLine.type === 'audio') {
124
+            if (mLine.type === MediaType.AUDIO) {
79 125
                 return;
80 126
             }
81 127
             if (!mLine.ssrcGroups || !mLine.ssrcGroups.length) {
@@ -97,24 +143,6 @@ export class TPCUtils {
97 143
         });
98 144
     }
99 145
 
100
-    /**
101
-     * Obtains stream encodings that need to be configured on the given track based
102
-     * on the track media type and the simulcast setting.
103
-     * @param {JitsiLocalTrack} localTrack
104
-     */
105
-    _getStreamEncodings(localTrack) {
106
-        if (this.pc.isSimulcastOn() && localTrack.isVideoTrack()) {
107
-            return this.localStreamEncodingsConfig;
108
-        }
109
-
110
-        return localTrack.isVideoTrack()
111
-            ? [ {
112
-                active: true,
113
-                maxBitrate: this.videoBitrates.high
114
-            } ]
115
-            : [ { active: true } ];
116
-    }
117
-
118 146
     /**
119 147
      * Takes in a *unified plan* offer and inserts the appropriate
120 148
      * parameters for adding simulcast receive support.
@@ -126,20 +154,19 @@ export class TPCUtils {
126 154
      * with its sdp field modified to advertise simulcast receive support
127 155
      */
128 156
     insertUnifiedPlanSimulcastReceive(desc) {
129
-        // a=simulcast line is not needed on browsers where
130
-        // we munge SDP for turning on simulcast. Remove this check
131
-        // when we move to RID/MID based simulcast on all browsers.
157
+        // a=simulcast line is not needed on browsers where we SDP munging is used for enabling on simulcast.
158
+        // Remove this check when the client switches to RID/MID based simulcast on all browsers.
132 159
         if (browser.usesSdpMungingForSimulcast()) {
133 160
             return desc;
134 161
         }
135 162
         const sdp = transform.parse(desc.sdp);
136
-        const idx = sdp.media.findIndex(mline => mline.type === 'video');
163
+        const idx = sdp.media.findIndex(mline => mline.type === MediaType.VIDEO);
137 164
 
138 165
         if (sdp.media[idx].rids && (sdp.media[idx].simulcast_03 || sdp.media[idx].simulcast)) {
139 166
             // Make sure we don't have the simulcast recv line on video descriptions other than
140 167
             // the first video description.
141 168
             sdp.media.forEach((mline, i) => {
142
-                if (mline.type === 'video' && i !== idx) {
169
+                if (mline.type === MediaType.VIDEO && i !== idx) {
143 170
                     sdp.media[i].rids = undefined;
144 171
                     sdp.media[i].simulcast = undefined;
145 172
 
@@ -201,7 +228,7 @@ export class TPCUtils {
201 228
             // Use pc.addTransceiver() for the initiator case when local tracks are getting added
202 229
             // to the peerconnection before a session-initiate is sent over to the peer.
203 230
             const transceiverInit = {
204
-                direction: 'sendrecv',
231
+                direction: TransceiverDirection.SENDRECV,
205 232
                 streams: [ localTrack.getOriginalStream() ],
206 233
                 sendEncodings: []
207 234
             };
@@ -226,39 +253,13 @@ export class TPCUtils {
226 253
     addTrackUnmute(localTrack) {
227 254
         const mediaType = localTrack.getType();
228 255
         const track = localTrack.getTrack();
229
-
230
-        // The assumption here is that the first transceiver of the specified
231
-        // media type is that of the local track.
232
-        const transceiver = this.pc.peerconnection.getTransceivers()
233
-            .find(t => t.receiver && t.receiver.track && t.receiver.track.kind === mediaType);
256
+        const transceiver = this._findTransceiver(mediaType);
234 257
 
235 258
         if (!transceiver) {
236 259
             return Promise.reject(new Error(`RTCRtpTransceiver for ${mediaType} not found`));
237 260
         }
238 261
         logger.debug(`Adding ${localTrack} on ${this.pc}`);
239 262
 
240
-        // If the client starts with audio/video muted setting, the transceiver direction will be set to 'recvonly'.
241
-        if (transceiver.direction === 'recvonly') {
242
-            const stream = localTrack.getOriginalStream();
243
-
244
-            if (stream && track) {
245
-                try {
246
-                    this.pc.peerconnection.addTrack(track, stream);
247
-                } catch (error) {
248
-                    logger.error(`Adding ${localTrack} failed on ${this.pc}:${error?.message}`);
249
-
250
-                    return Promise.reject(error);
251
-                }
252
-
253
-                return this.setEncodings(localTrack).then(() => {
254
-                    this.pc.localTracks.set(localTrack.rtcId, localTrack);
255
-                    transceiver.direction = 'sendrecv';
256
-                });
257
-            }
258
-
259
-            return Promise.resolve();
260
-        }
261
-
262 263
         return transceiver.sender.replaceTrack(track);
263 264
     }
264 265
 
@@ -295,8 +296,7 @@ export class TPCUtils {
295 296
      */
296 297
     removeTrackMute(localTrack) {
297 298
         const mediaType = localTrack.getType();
298
-        const transceiver = this.pc.peerconnection.getTransceivers()
299
-            .find(t => t.sender && t.sender.track && t.sender.track.id === localTrack.getTrackId());
299
+        const transceiver = this._findTransceiver(mediaType, localTrack);
300 300
 
301 301
         if (!transceiver) {
302 302
             return Promise.reject(new Error(`RTCRtpTransceiver for ${mediaType} not found`));
@@ -316,7 +316,7 @@ export class TPCUtils {
316 316
     replaceTrack(oldTrack, newTrack) {
317 317
         if (oldTrack && newTrack) {
318 318
             const mediaType = newTrack.getType();
319
-            const stream = newTrack.getOriginalStream();
319
+            const stream = newTrack.stream;
320 320
 
321 321
             // Ignore cases when the track is replaced while the device is in a muted state,like
322 322
             // replacing camera when video muted or replacing mic when audio muted. These JitsiLocalTracks
@@ -328,11 +328,11 @@ export class TPCUtils {
328 328
 
329 329
                 return Promise.resolve();
330 330
             }
331
-            const track = mediaType === MediaType.AUDIO
332
-                ? stream.getAudioTracks()[0]
333
-                : stream.getVideoTracks()[0];
334
-            const transceiver = this.pc.peerconnection.getTransceivers()
335
-                .find(t => t.receiver.track.kind === mediaType && !t.stopped);
331
+
332
+            const transceiver = oldTrack.isVideoTrack() && browser.doesVideoMuteByStreamRemove && oldTrack.isMuted()
333
+                ? this._findTransceiver(mediaType)
334
+                : this._findTransceiver(mediaType, oldTrack);
335
+            const track = mediaType === MediaType.AUDIO ? stream.getAudioTracks()[0] : stream.getVideoTracks()[0];
336 336
 
337 337
             if (!transceiver) {
338 338
                 return Promise.reject(new Error('replace track failed'));
@@ -357,6 +357,16 @@ export class TPCUtils {
357 357
         } else if (oldTrack && !newTrack) {
358 358
             return this.removeTrackMute(oldTrack)
359 359
                 .then(() => {
360
+                    const mediaType = oldTrack.getType();
361
+                    const transceiver = this._findTransceiver(mediaType);
362
+
363
+                    // Change the direction on the transceiver to 'recvonly' so that a 'removetrack'
364
+                    // is fired on the associated media stream on the remote peer.
365
+                    if (transceiver) {
366
+                        transceiver.direction = TransceiverDirection.RECVONLY;
367
+                    }
368
+
369
+                    // Remove the old track from the list of local tracks.
360 370
                     this.pc.localTracks.delete(oldTrack.rtcId);
361 371
                     this.pc.localSSRCs.delete(oldTrack.rtcId);
362 372
                 });
@@ -364,7 +374,18 @@ export class TPCUtils {
364 374
             const ssrc = this.pc.localSSRCs.get(newTrack.rtcId);
365 375
 
366 376
             return this.addTrackUnmute(newTrack)
377
+                .then(() => this.setEncodings(newTrack))
367 378
                 .then(() => {
379
+                    const mediaType = newTrack.getType();
380
+                    const transceiver = this._findTransceiver(mediaType, newTrack);
381
+
382
+                    // Change the direction on the transceiver back to 'sendrecv' so that a 'track'
383
+                    // event is fired on the remote peer.
384
+                    if (transceiver) {
385
+                        transceiver.direction = TransceiverDirection.SENDRECV;
386
+                    }
387
+
388
+                    // Add the new track to the list of local tracks.
368 389
                     this.pc.localTracks.set(newTrack.rtcId, newTrack);
369 390
                     this.pc.localSSRCs.set(newTrack.rtcId, ssrc);
370 391
                 });
@@ -395,9 +416,9 @@ export class TPCUtils {
395 416
      * @returns {Promise<void>} - resolved when done.
396 417
      */
397 418
     setEncodings(track) {
398
-        const transceiver = this.pc.peerconnection.getTransceivers()
399
-            .find(t => t.sender && t.sender.track && t.sender.track.kind === track.getType());
400
-        const parameters = transceiver.sender.getParameters();
419
+        const mediaType = track.getType();
420
+        const transceiver = this._findTransceiver(mediaType, track);
421
+        const parameters = transceiver?.sender?.getParameters();
401 422
 
402 423
         // Resolve if the encodings are not available yet. This happens immediately after the track is added to the
403 424
         // peerconnection on chrome in unified-plan. It is ok to ignore and not report the error here since the
@@ -428,12 +449,12 @@ export class TPCUtils {
428 449
             if (active) {
429 450
                 // The first transceiver is for the local track and only this one can be set to 'sendrecv'
430 451
                 if (idx === 0 && localTracks.length) {
431
-                    transceiver.direction = 'sendrecv';
452
+                    transceiver.direction = TransceiverDirection.SENDRECV;
432 453
                 } else {
433
-                    transceiver.direction = 'recvonly';
454
+                    transceiver.direction = TransceiverDirection.RECVONLY;
434 455
                 }
435 456
             } else {
436
-                transceiver.direction = 'inactive';
457
+                transceiver.direction = TransceiverDirection.INACTIVE;
437 458
             }
438 459
         });
439 460
     }

+ 10
- 17
modules/RTC/TraceablePeerConnection.js Parādīt failu

@@ -315,14 +315,15 @@ export default function TraceablePeerConnection(
315 315
         this.peerconnection.onremovestream
316 316
             = event => this._remoteStreamRemoved(event.stream);
317 317
     } else {
318
-        this.peerconnection.ontrack = event => {
319
-            const stream = event.streams[0];
318
+        this.onTrack = evt => {
319
+            const stream = evt.streams[0];
320 320
 
321
-            this._remoteTrackAdded(stream, event.track, event.transceiver);
322
-            stream.onremovetrack = evt => {
323
-                this._remoteTrackRemoved(stream, evt.track);
324
-            };
321
+            this._remoteTrackAdded(stream, evt.track, evt.transceiver);
322
+            stream.addEventListener('removetrack', e => {
323
+                this._remoteTrackRemoved(stream, e.track);
324
+            });
325 325
         };
326
+        this.peerconnection.addEventListener('track', this.onTrack);
326 327
     }
327 328
     this.onsignalingstatechange = null;
328 329
     this.peerconnection.onsignalingstatechange = event => {
@@ -954,13 +955,6 @@ TraceablePeerConnection.prototype._createRemoteTrack = function(
954 955
 
955 956
     const existingTrack = remoteTracksMap.get(mediaType);
956 957
 
957
-    // Delete the existing track and create the new one because of a known bug on Safari.
958
-    // RTCPeerConnection.ontrack fires when a new remote track is added but MediaStream.onremovetrack doesn't so
959
-    // it needs to be removed whenever a new track is received for the same endpoint id.
960
-    if (existingTrack && browser.isWebKitBased()) {
961
-        this._remoteTrackRemoved(existingTrack.getOriginalStream(), existingTrack.getTrack());
962
-    }
963
-
964 958
     if (existingTrack && existingTrack.getTrack() === track) {
965 959
         // Ignore duplicated event which can originate either from 'onStreamAdded' or 'onTrackAdded'.
966 960
         logger.info(
@@ -2723,10 +2717,9 @@ TraceablePeerConnection.prototype.close = function() {
2723 2717
     this.trace('stop');
2724 2718
 
2725 2719
     // Off SignalingEvents
2726
-    this.signalingLayer.off(
2727
-        SignalingEvents.PEER_MUTED_CHANGED, this._peerMutedChanged);
2728
-    this.signalingLayer.off(
2729
-        SignalingEvents.PEER_VIDEO_TYPE_CHANGED, this._peerVideoTypeChanged);
2720
+    this.signalingLayer.off(SignalingEvents.PEER_MUTED_CHANGED, this._peerMutedChanged);
2721
+    this.signalingLayer.off(SignalingEvents.PEER_VIDEO_TYPE_CHANGED, this._peerVideoTypeChanged);
2722
+    browser.usesUnifiedPlan() && this.peerconnection.removeEventListener('track', this.onTrack);
2730 2723
 
2731 2724
     for (const peerTracks of this.remoteTracks.values()) {
2732 2725
         for (const remoteTrack of peerTracks.values()) {

Notiek ielāde…
Atcelt
Saglabāt