浏览代码

fix(unified-plan): use RTCRtpSender.replaceTrack for mute/unmute/replace operations

Trigger renegotation only when negotiationneeded event is fired on the browser.
Do not disable simulcast for screensharing in unified plan.
Fix the order of the simulcast streams for Firefox
dev1
Jaya Allamsetty 5 年前
父节点
当前提交
1c790e3049
共有 3 个文件被更改,包括 532 次插入226 次删除
  1. 365
    0
      modules/RTC/TPCUtils.js
  2. 66
    156
      modules/RTC/TraceablePeerConnection.js
  3. 101
    70
      modules/xmpp/JingleSessionPC.js

+ 365
- 0
modules/RTC/TPCUtils.js 查看文件

1
+import { getLogger } from 'jitsi-meet-logger';
2
+import transform from 'sdp-transform';
3
+
4
+import * as JitsiTrackEvents from '../../JitsiTrackEvents';
5
+import browser from '../browser';
6
+import RTCEvents from '../../service/RTC/RTCEvents';
7
+
8
+const logger = getLogger(__filename);
9
+const SIM_LAYER_1_RID = '1';
10
+const SIM_LAYER_2_RID = '2';
11
+const SIM_LAYER_3_RID = '3';
12
+
13
+export const SIM_LAYER_RIDS = [ SIM_LAYER_1_RID, SIM_LAYER_2_RID, SIM_LAYER_3_RID ];
14
+
15
+/**
16
+ * Handles track related operations on TraceablePeerConnection when browser is
17
+ * running in unified plan mode.
18
+ */
19
+export class TPCUtils {
20
+    /**
21
+     * @constructor
22
+     */
23
+    constructor(peerconnection) {
24
+        this.pc = peerconnection;
25
+
26
+        /**
27
+         * The simulcast encodings that will be configured on the RTCRtpSender
28
+         * for the video tracks in the unified plan mode.
29
+         */
30
+        this.simulcastEncodings = [
31
+            {
32
+                active: true,
33
+                maxBitrate: browser.isFirefox() ? 2500000 : 200000,
34
+                rid: SIM_LAYER_1_RID,
35
+                scaleResolutionDownBy: browser.isFirefox() ? 1.0 : 4.0
36
+            },
37
+            {
38
+                active: true,
39
+                maxBitrate: 700000,
40
+                rid: SIM_LAYER_2_RID,
41
+                scaleResolutionDownBy: 2.0
42
+            },
43
+            {
44
+                active: true,
45
+                maxBitrate: browser.isFirefox() ? 200000 : 2500000,
46
+                rid: SIM_LAYER_3_RID,
47
+                scaleResolutionDownBy: browser.isFirefox() ? 4.0 : 1.0
48
+            }
49
+        ];
50
+    }
51
+
52
+    /**
53
+     * Obtains local tracks for given {@link MediaType}.
54
+     * @param {MediaType} mediaType - audio or video.
55
+     * @return {Array<JitsiLocalTrack>} - array containing the local tracks
56
+     * attached to the peerconnection of the given media type.
57
+     */
58
+    _getLocalTracks(mediaType) {
59
+        const tracks = Array.from(this.pc.localTracks.values());
60
+
61
+        return tracks.filter(track => track.getType() === mediaType);
62
+    }
63
+
64
+    /**
65
+     * Obtains stream encodings that need to be configured on the given track.
66
+     * @param {JitsiLocalTrack} localTrack
67
+     */
68
+    _getStreamEncodings(localTrack) {
69
+        if (this.pc.isSimulcastOn() && localTrack.isVideoTrack()) {
70
+            return this.simulcastEncodings;
71
+        }
72
+
73
+        return [ { active: true } ];
74
+    }
75
+
76
+    /**
77
+     * Takes in a *unified plan* offer and inserts the appropriate
78
+     * parameters for adding simulcast receive support.
79
+     * @param {Object} desc - A session description object
80
+     * @param {String} desc.type - the type (offer/answer)
81
+     * @param {String} desc.sdp - the sdp content
82
+     *
83
+     * @return {Object} A session description (same format as above) object
84
+     * with its sdp field modified to advertise simulcast receive support
85
+     */
86
+    _insertUnifiedPlanSimulcastReceive(desc) {
87
+        // a=simulcast line is not needed on browsers where
88
+        // we munge SDP for turning on simulcast. Remove this check
89
+        // when we move to RID/MID based simulcast on all browsers.
90
+        if (browser.usesSdpMungingForSimulcast()) {
91
+            return desc;
92
+        }
93
+        const sdp = transform.parse(desc.sdp);
94
+        const idx = sdp.media.findIndex(mline => mline.type === 'video');
95
+
96
+        if (sdp.media[idx].rids && (sdp.media[idx].simulcast_03 || sdp.media[idx].simulcast)) {
97
+            // Make sure we don't have the simulcast recv line on video descriptions other than the
98
+            // the first video description.
99
+            sdp.media.forEach((mline, i) => {
100
+                if (mline.type === 'video' && i !== idx) {
101
+                    sdp.media[i].rids = undefined;
102
+                    sdp.media[i].simulcast = undefined;
103
+                }
104
+            });
105
+        }
106
+
107
+        // In order of highest to lowest spatial quality
108
+        sdp.media[idx].rids = [
109
+            {
110
+                id: SIM_LAYER_1_RID,
111
+                direction: 'recv'
112
+            },
113
+            {
114
+                id: SIM_LAYER_2_RID,
115
+                direction: 'recv'
116
+            },
117
+            {
118
+                id: SIM_LAYER_3_RID,
119
+                direction: 'recv'
120
+            }
121
+        ];
122
+
123
+        // Firefox 72 has stopped parsing the legacy rid= parameters in simulcast attributes.
124
+        // eslint-disable-next-line max-len
125
+        // https://www.fxsitecompat.dev/en-CA/docs/2019/pt-and-rid-in-webrtc-simulcast-attributes-are-no-longer-supported/
126
+        const simulcastLine = browser.isFirefox() && browser.isVersionGreaterThan(71)
127
+            ? `recv ${SIM_LAYER_RIDS.join(';')}`
128
+            : `recv rid=${SIM_LAYER_RIDS.join(';')}`;
129
+
130
+        // eslint-disable-next-line camelcase
131
+        sdp.media[idx].simulcast_03 = {
132
+            value: simulcastLine
133
+        };
134
+
135
+        return new RTCSessionDescription({
136
+            type: desc.type,
137
+            sdp: transform.write(sdp)
138
+        });
139
+    }
140
+
141
+    /**
142
+    * Adds {@link JitsiLocalTrack} to the WebRTC peerconnection for the first time.
143
+    * @param {JitsiLocalTrack} track - track to be added to the peerconnection.
144
+    * @returns {boolean} Returns true if the operation is successful,
145
+    * false otherwise.
146
+    */
147
+    addTrack(localTrack, isInitiator = true) {
148
+        const track = localTrack.getTrack();
149
+
150
+        if (isInitiator) {
151
+            // Use pc.addTransceiver() for the initiator case when local tracks are getting added
152
+            // to the peerconnection before a session-initiate is sent over to the peer.
153
+            const transceiverInit = {
154
+                direction: 'sendrecv',
155
+                streams: [ localTrack.getOriginalStream() ],
156
+                sendEncodings: []
157
+            };
158
+
159
+            if (!browser.isFirefox()) {
160
+                transceiverInit.sendEncodings = this._getStreamEncodings(localTrack);
161
+            }
162
+            this.pc.peerconnection.addTransceiver(track, transceiverInit);
163
+        } else {
164
+            // Use pc.addTrack() for responder case so that we can re-use the m-lines that were created
165
+            // when setRemoteDescription was called. pc.addTrack() automatically  attaches to any existing
166
+            // unused "recv-only" transceiver.
167
+            this.pc.peerconnection.addTrack(track);
168
+        }
169
+    }
170
+
171
+    /**
172
+     * Adds a track on the RTCRtpSender as part of the unmute operation.
173
+     * @param {JitsiLocalTrack} localTrack - track to be unmuted.
174
+     * @returns {boolean} Returns true if the operation is successful,
175
+     * false otherwise.
176
+     */
177
+    addTrackUnmute(localTrack) {
178
+        const mediaType = localTrack.getType();
179
+        const track = localTrack.getTrack();
180
+
181
+        // The assumption here is that the first transceiver of the specified
182
+        // media type is that of the local track.
183
+        const transceiver = this.pc.peerconnection.getTransceivers()
184
+            .find(t => t.receiver && t.receiver.track && t.receiver.track.kind === mediaType);
185
+
186
+        if (!transceiver) {
187
+            logger.error(`RTCRtpTransceiver for ${mediaType} on ${this.pc} not found`);
188
+
189
+            return false;
190
+        }
191
+        logger.info(`Adding ${localTrack} on ${this.pc}`);
192
+
193
+        // If the client starts with audio/video muted setting, the transceiver direction
194
+        // will be set to 'recvonly'. Use addStream here so that a MSID is generated for the stream.
195
+        if (transceiver.direction === 'recvonly') {
196
+            this.pc.peerconnection.addStream(localTrack.getOriginalStream());
197
+            this.setEncodings(localTrack);
198
+            this.pc.localTracks.set(localTrack.rtcId, localTrack);
199
+            transceiver.direction = 'sendrecv';
200
+
201
+            return true;
202
+        }
203
+        transceiver.sender.replaceTrack(track)
204
+            .then(() => {
205
+                this.pc.localTracks.set(localTrack.rtcId, localTrack);
206
+
207
+                return true;
208
+            })
209
+            .catch(err => {
210
+                logger.error(`Unmute track failed for ${mediaType} track on ${this.pc}, ${err}`);
211
+
212
+                return false;
213
+            });
214
+    }
215
+
216
+    /**
217
+     * Removes the track from the RTCRtpSender as part of the mute operation.
218
+     * @param {JitsiLocalTrack} localTrack - track to be removed.
219
+     * @returns {boolean} Returns true if the operation is successful,
220
+     * false otherwise.
221
+     */
222
+    removeTrackMute(localTrack) {
223
+        const mediaType = localTrack.getType();
224
+        const transceiver = this.pc.peerconnection.getTransceivers()
225
+            .find(t => t.sender && t.sender.track && t.sender.track.id === localTrack.getTrackId());
226
+
227
+        if (!transceiver) {
228
+            logger.error(`RTCRtpTransceiver for ${mediaType} on ${this.pc} not found`);
229
+
230
+            return false;
231
+        }
232
+
233
+        logger.info(`Removing ${localTrack} on ${this.pc}`);
234
+        transceiver.sender.replaceTrack(null)
235
+            .then(() => {
236
+                this.pc.localTracks.delete(localTrack.rtcId);
237
+                this.pc.localSSRCs.delete(localTrack.rtcId);
238
+
239
+                return true;
240
+            })
241
+            .catch(err => {
242
+                logger.error(`Mute track failed for ${mediaType} track on ${this.pc}, ${err}`);
243
+
244
+                return false;
245
+            });
246
+    }
247
+
248
+    /**
249
+     * Replaces the existing track on a RTCRtpSender with the given track.
250
+     * @param {JitsiLocalTrack} oldTrack - existing track on the sender that needs to be removed.
251
+     * @param {JitsiLocalTrack} newTrack - new track that needs to be added to the sender.
252
+     * @returns {Promise<false>} Promise that resolves with false as we don't want
253
+     * renegotiation to be triggered automatically after this operation. Renegotiation is
254
+     * done when the browser fires the negotiationeeded event.
255
+     */
256
+    replaceTrack(oldTrack, newTrack) {
257
+        if (oldTrack && newTrack) {
258
+            const mediaType = newTrack.getType();
259
+            const stream = newTrack.getOriginalStream();
260
+            const track = stream.getVideoTracks()[0];
261
+            const transceiver = this.pc.peerconnection.getTransceivers()
262
+                .find(t => t.receiver.track.kind === mediaType && !t.stopped);
263
+
264
+            if (!transceiver) {
265
+                return Promise.reject(new Error('replace track failed'));
266
+            }
267
+
268
+            return transceiver.sender.replaceTrack(track)
269
+                .then(() => {
270
+                    const ssrc = this.pc.localSSRCs.get(oldTrack.rtcId);
271
+
272
+                    this.pc.localTracks.delete(oldTrack.rtcId);
273
+                    this.pc.localSSRCs.delete(oldTrack.rtcId);
274
+                    this.pc._addedStreams = this.pc._addedStreams.filter(s => s !== stream);
275
+                    this.pc.localTracks.set(newTrack.rtcId, newTrack);
276
+
277
+                    this.pc._addedStreams.push(stream);
278
+                    this.pc.localSSRCs.set(newTrack.rtcId, ssrc);
279
+                    this.pc.eventEmitter.emit(RTCEvents.LOCAL_TRACK_SSRC_UPDATED,
280
+                        newTrack,
281
+                        this.pc._extractPrimarySSRC(ssrc));
282
+                });
283
+        } else if (oldTrack && !newTrack) {
284
+            if (!this.removeTrackMute(oldTrack)) {
285
+                return Promise.reject(new Error('replace track failed'));
286
+            }
287
+            this.pc.localTracks.delete(oldTrack.rtcId);
288
+            this.pc.localSSRCs.delete(oldTrack.rtcId);
289
+        } else if (newTrack && !oldTrack) {
290
+            const ssrc = this.pc.localSSRCs.get(newTrack.rtcId);
291
+
292
+            if (!this.addTrackUnmute(newTrack)) {
293
+                return Promise.reject(new Error('replace track failed'));
294
+            }
295
+            newTrack.emit(JitsiTrackEvents.TRACK_MUTE_CHANGED, newTrack);
296
+            this.pc.localTracks.set(newTrack.rtcId, newTrack);
297
+            this.pc.localSSRCs.set(newTrack.rtcId, ssrc);
298
+        }
299
+
300
+        return Promise.resolve(false);
301
+    }
302
+
303
+    /**
304
+     *
305
+     * @param {boolean} active
306
+     */
307
+    setAudioTransferActive(active) {
308
+        return this.setMediaTransferActive('audio', active);
309
+    }
310
+
311
+    /**
312
+     * Set the simulcast stream encoding properties on the RTCRtpSender.
313
+     * @param {*} track - the current track in use for which the encodings are to be set.
314
+     */
315
+    setEncodings(track) {
316
+        const transceiver = this.pc.peerconnection.getTransceivers()
317
+            .find(t => t.sender && t.sender.track && t.sender.track.kind === track.getType());
318
+        const parameters = transceiver.sender.getParameters();
319
+
320
+        parameters.encodings = this._getStreamEncodings(track);
321
+        transceiver.sender.setParameters(parameters);
322
+    }
323
+
324
+    /**
325
+     *
326
+     * @param {*} mediaType
327
+     * @param {boolean} active
328
+     */
329
+    setMediaTransferActive(mediaType, active) {
330
+        const transceivers = this.pc.peerconnection.getTransceivers()
331
+            .filter(t => t.receiver && t.receiver.track && t.receiver.track.kind === mediaType);
332
+
333
+        if (active) {
334
+            transceivers.forEach(transceiver => {
335
+                if (this._getLocalTracks(mediaType).length > 0) {
336
+                    transceiver.direction = 'sendrecv';
337
+                    const parameters = transceiver.sender.getParameters();
338
+
339
+                    if (parameters && parameters.encodings && parameters.encodings.length) {
340
+                        parameters.encodings.forEach(encoding => {
341
+                            encoding.active = true;
342
+                        });
343
+                        transceiver.sender.setParameters(parameters);
344
+                    }
345
+                } else {
346
+                    transceiver.direction = 'recvonly';
347
+                }
348
+            });
349
+        } else {
350
+            transceivers.forEach(transceiver => {
351
+                transceiver.direction = 'inactive';
352
+            });
353
+        }
354
+
355
+        return true;
356
+    }
357
+
358
+    /**
359
+     *
360
+     * @param {boolean} active
361
+     */
362
+    setVideoTransferActive(active) {
363
+        return this.setMediaTransferActive('video', active);
364
+    }
365
+}

+ 66
- 156
modules/RTC/TraceablePeerConnection.js 查看文件

5
 
5
 
6
 import * as GlobalOnErrorHandler from '../util/GlobalOnErrorHandler';
6
 import * as GlobalOnErrorHandler from '../util/GlobalOnErrorHandler';
7
 import JitsiRemoteTrack from './JitsiRemoteTrack';
7
 import JitsiRemoteTrack from './JitsiRemoteTrack';
8
-import * as JitsiTrackEvents from '../../JitsiTrackEvents';
9
 import * as MediaType from '../../service/RTC/MediaType';
8
 import * as MediaType from '../../service/RTC/MediaType';
10
 import LocalSdpMunger from './LocalSdpMunger';
9
 import LocalSdpMunger from './LocalSdpMunger';
11
 import RTC from './RTC';
10
 import RTC from './RTC';
13
 import browser from '../browser';
12
 import browser from '../browser';
14
 import RTCEvents from '../../service/RTC/RTCEvents';
13
 import RTCEvents from '../../service/RTC/RTCEvents';
15
 import RtxModifier from '../xmpp/RtxModifier';
14
 import RtxModifier from '../xmpp/RtxModifier';
15
+import { SIM_LAYER_RIDS, TPCUtils } from './TPCUtils';
16
 
16
 
17
 // FIXME SDP tools should end up in some kind of util module
17
 // FIXME SDP tools should end up in some kind of util module
18
 import SDP from '../xmpp/SDP';
18
 import SDP from '../xmpp/SDP';
22
 import * as SignalingEvents from '../../service/RTC/SignalingEvents';
22
 import * as SignalingEvents from '../../service/RTC/SignalingEvents';
23
 
23
 
24
 const logger = getLogger(__filename);
24
 const logger = getLogger(__filename);
25
-const SIMULCAST_LAYERS = 3;
26
-const SIM_LAYER_1_RID = '1';
27
-const SIM_LAYER_2_RID = '2';
28
-const SIM_LAYER_3_RID = '3';
29
-const SIM_LAYER_RIDS = [ SIM_LAYER_1_RID, SIM_LAYER_2_RID, SIM_LAYER_3_RID ];
30
-const SIM_LAYER_BITRATES_BPS = [ 200000, 700000, 2500000 ];
25
+const MAX_BITRATE = 2500000;
31
 const DESKSTOP_SHARE_RATE = 500000;
26
 const DESKSTOP_SHARE_RATE = 500000;
32
 
27
 
33
 /* eslint-disable max-params */
28
 /* eslint-disable max-params */
215
 
210
 
216
     this.peerconnection
211
     this.peerconnection
217
         = new RTCUtils.RTCPeerConnectionType(iceConfig, constraints);
212
         = new RTCUtils.RTCPeerConnectionType(iceConfig, constraints);
213
+    this.tpcUtils = new TPCUtils(this);
218
     this.updateLog = [];
214
     this.updateLog = [];
219
     this.stats = {};
215
     this.stats = {};
220
     this.statsinterval = null;
216
     this.statsinterval = null;
229
     this.interop = new Interop();
225
     this.interop = new Interop();
230
     const Simulcast = require('@jitsi/sdp-simulcast');
226
     const Simulcast = require('@jitsi/sdp-simulcast');
231
 
227
 
232
-    this.simulcast = new Simulcast({ numOfLayers: SIMULCAST_LAYERS,
233
-        explodeRemoteSimulcast: false });
228
+    this.simulcast = new Simulcast(
229
+        {
230
+            numOfLayers: SIM_LAYER_RIDS.length,
231
+            explodeRemoteSimulcast: false,
232
+            usesUnifiedPlan: browser.usesUnifiedPlan()
233
+        });
234
     this.sdpConsistency = new SdpConsistency(this.toString());
234
     this.sdpConsistency = new SdpConsistency(this.toString());
235
 
235
 
236
     /**
236
     /**
1268
         const sdp = transform.parse(desc.sdp);
1268
         const sdp = transform.parse(desc.sdp);
1269
         const video = sdp.media.find(mline => mline.type === 'video');
1269
         const video = sdp.media.find(mline => mline.type === 'video');
1270
 
1270
 
1271
-        if (video.simulcast_03) {
1271
+        if (video.simulcast) {
1272
             const ssrcs = [];
1272
             const ssrcs = [];
1273
 
1273
 
1274
             video.ssrcs.forEach(ssrc => {
1274
             video.ssrcs.forEach(ssrc => {
1377
  * Add {@link JitsiLocalTrack} to this TPC.
1377
  * Add {@link JitsiLocalTrack} to this TPC.
1378
  * @param {JitsiLocalTrack} track
1378
  * @param {JitsiLocalTrack} track
1379
  */
1379
  */
1380
-TraceablePeerConnection.prototype.addTrack = function(track) {
1380
+TraceablePeerConnection.prototype.addTrack = function(track, isInitiator = false) {
1381
     const rtcId = track.rtcId;
1381
     const rtcId = track.rtcId;
1382
 
1382
 
1383
     logger.info(`add ${track} to: ${this}`);
1383
     logger.info(`add ${track} to: ${this}`);
1389
     }
1389
     }
1390
 
1390
 
1391
     this.localTracks.set(rtcId, track);
1391
     this.localTracks.set(rtcId, track);
1392
+    if (browser.usesUnifiedPlan() && isInitiator) {
1393
+        return this.tpcUtils.addTrack(track, isInitiator);
1394
+    }
1392
 
1395
 
1393
     const webrtcStream = track.getOriginalStream();
1396
     const webrtcStream = track.getOriginalStream();
1394
 
1397
 
1430
             this.rtxModifier.setSsrcCache(rtxSsrcMapping);
1433
             this.rtxModifier.setSsrcCache(rtxSsrcMapping);
1431
         }
1434
         }
1432
     }
1435
     }
1436
+    if (browser.usesUnifiedPlan() && !browser.usesSdpMungingForSimulcast()) {
1437
+        this.tpcUtils.setEncodings(track);
1438
+    }
1433
 };
1439
 };
1434
 
1440
 
1435
 /**
1441
 /**
1446
     }
1452
     }
1447
 
1453
 
1448
     logger.info(`Adding ${track} as unmute to ${this}`);
1454
     logger.info(`Adding ${track} as unmute to ${this}`);
1455
+    if (browser.usesUnifiedPlan()) {
1456
+        return this.tpcUtils.addTrackUnmute(track);
1457
+    }
1449
     const webRtcStream = track.getOriginalStream();
1458
     const webRtcStream = track.getOriginalStream();
1450
 
1459
 
1451
     if (!webRtcStream) {
1460
     if (!webRtcStream) {
1525
  *       The same applies to addTrack.
1534
  *       The same applies to addTrack.
1526
  */
1535
  */
1527
 TraceablePeerConnection.prototype.removeTrack = function(localTrack) {
1536
 TraceablePeerConnection.prototype.removeTrack = function(localTrack) {
1537
+    if (browser.usesUnifiedPlan()) {
1538
+        return this.tpcUtils.removeTrack(localTrack);
1539
+    }
1528
     const webRtcStream = localTrack.getOriginalStream();
1540
     const webRtcStream = localTrack.getOriginalStream();
1529
 
1541
 
1530
     this.trace(
1542
     this.trace(
1590
  * renegotiation will be needed. Otherwise no renegotiation is needed.
1602
  * renegotiation will be needed. Otherwise no renegotiation is needed.
1591
  */
1603
  */
1592
 TraceablePeerConnection.prototype.replaceTrack = function(oldTrack, newTrack) {
1604
 TraceablePeerConnection.prototype.replaceTrack = function(oldTrack, newTrack) {
1593
-    if (browser.supportsRtpSender() && oldTrack && newTrack) {
1594
-        // Add and than remove stream in FF leads to wrong local SDP. In order
1595
-        // to workaround the issue we need to use sender.replaceTrack().
1596
-        const sender = this.findSenderByStream(oldTrack.getOriginalStream());
1597
-        const stream = newTrack.getOriginalStream();
1598
-
1599
-        if (sender && stream) {
1600
-            const track = stream.getTracks()[0];
1601
-
1602
-            if (track) {
1603
-                return sender.replaceTrack(track, stream).then(() => {
1604
-                    // Since there is no need to do renegotiations we need to
1605
-                    // fix all ssrc-msid mappings here.
1606
-                    // NOTE: after sender.replaceTrack the sdp will remain the
1607
-                    // same but the stream attach to the new JitsiLocalTrack
1608
-                    // will have different msid. Luckily on FF we are not doing
1609
-                    // all the transformations related to video mute.
1610
-
1611
-                    const ssrc = this.localSSRCs.get(oldTrack.rtcId);
1612
-
1613
-                    this.localTracks.delete(oldTrack.rtcId);
1614
-                    this.localSSRCs.delete(oldTrack.rtcId);
1615
-                    this._addedStreams
1616
-                        = this._addedStreams.filter(s => s !== stream);
1617
-
1618
-                    this.localTracks.set(newTrack.rtcId, newTrack);
1619
-
1620
-                    // Override the msid of JitsiLocalTrack in order to be
1621
-                    // consistent with the SDP values.
1622
-                    newTrack.storedMSID = oldTrack.storedMSID;
1623
-                    this._addedStreams.push(stream);
1624
-
1625
-                    this.localSSRCs.set(newTrack.rtcId, ssrc);
1626
-                    this.eventEmitter.emit(
1627
-                        RTCEvents.LOCAL_TRACK_SSRC_UPDATED,
1628
-                        newTrack,
1629
-                        extractPrimarySSRC(ssrc));
1630
-
1631
-                    return false;
1632
-                });
1633
-            }
1634
-        }
1605
+    if (browser.usesUnifiedPlan()) {
1606
+        return this.tpcUtils.replaceTrack(oldTrack, newTrack);
1635
     }
1607
     }
1636
-
1637
     if (oldTrack) {
1608
     if (oldTrack) {
1638
         this.removeTrack(oldTrack);
1609
         this.removeTrack(oldTrack);
1639
     }
1610
     }
1644
     return Promise.resolve(true);
1615
     return Promise.resolve(true);
1645
 };
1616
 };
1646
 
1617
 
1647
-/**
1648
- * Replaces the existing media stream from the underlying peerconnection with the new
1649
- * mediastream that has been added to the JitsiLocalTrack. Renegotiation with the remote
1650
- * peer is not needed in this case.
1651
- * @param {JitsiLocalTrack} localTrack - the localtrack whose mediastream has been updated.
1652
- * @return {Promise} - Promise resolved with undefined if the track is replaced,
1653
- * or rejected with <tt>InvalidModificationError<tt> if the track cannot be replaced.
1654
- */
1655
-TraceablePeerConnection.prototype.replaceTrackWithoutOfferAnswer = function(localTrack) {
1656
-    const newTrack = localTrack.stream.getTracks()[0];
1657
-    const sender = this.findSenderByKind(newTrack.kind);
1658
-
1659
-    if (!sender) {
1660
-        return Promise.reject(new Error(`Could not find RTCRtpSender for ${newTrack.kind}`));
1661
-    }
1662
-
1663
-    return sender.replaceTrack(newTrack)
1664
-        .then(() => {
1665
-            this._addedStreams = this._addedStreams.filter(s => s !== localTrack._originalStream);
1666
-            this._addedStreams.push(localTrack.stream);
1667
-            localTrack.emit(JitsiTrackEvents.TRACK_MUTE_CHANGED, localTrack);
1668
-        })
1669
-        .catch(err => {
1670
-            logger.error(`replaceTrackWithoutOfferAnswer - replaceTrack failed for ${newTrack.kind}`, err);
1671
-        });
1672
-};
1673
-
1674
 /**
1618
 /**
1675
  * Removes local track as part of the mute operation.
1619
  * Removes local track as part of the mute operation.
1676
  * @param {JitsiLocalTrack} localTrack the local track to be remove as part of
1620
  * @param {JitsiLocalTrack} localTrack the local track to be remove as part of
1689
         // Abort - nothing to be done here
1633
         // Abort - nothing to be done here
1690
         return false;
1634
         return false;
1691
     }
1635
     }
1692
-
1636
+    if (browser.usesUnifiedPlan()) {
1637
+        return this.tpcUtils.removeTrackMute(localTrack);
1638
+    }
1693
     if (webRtcStream) {
1639
     if (webRtcStream) {
1694
         logger.info(
1640
         logger.info(
1695
             `Removing ${localTrack} as mute from ${this}`);
1641
             `Removing ${localTrack} as mute from ${this}`);
1896
  */
1842
  */
1897
 TraceablePeerConnection.prototype.setAudioTransferActive = function(active) {
1843
 TraceablePeerConnection.prototype.setAudioTransferActive = function(active) {
1898
     logger.debug(`${this} audio transfer active: ${active}`);
1844
     logger.debug(`${this} audio transfer active: ${active}`);
1845
+    if (browser.usesUnifiedPlan()) {
1846
+        return this.tpcUtils.setAudioTransferActive(active);
1847
+    }
1899
     const changed = this.audioTransferActive !== active;
1848
     const changed = this.audioTransferActive !== active;
1900
 
1849
 
1901
     this.audioTransferActive = active;
1850
     this.audioTransferActive = active;
1903
     return changed;
1852
     return changed;
1904
 };
1853
 };
1905
 
1854
 
1906
-/**
1907
- * Takes in a *unified plan* offer and inserts the appropriate
1908
- * parameters for adding simulcast receive support.
1909
- * @param {Object} desc - A session description object
1910
- * @param {String} desc.type - the type (offer/answer)
1911
- * @param {String} desc.sdp - the sdp content
1912
- *
1913
- * @return {Object} A session description (same format as above) object
1914
- * with its sdp field modified to advertise simulcast receive support
1915
- */
1916
-TraceablePeerConnection.prototype._insertUnifiedPlanSimulcastReceive
1917
-    = function(desc) {
1918
-        const sdp = transform.parse(desc.sdp);
1919
-        const video = sdp.media.find(mline => mline.type === 'video');
1920
-
1921
-        // In order of lowest to highest spatial quality
1922
-        video.rids = [
1923
-            {
1924
-                id: SIM_LAYER_1_RID,
1925
-                direction: 'recv'
1926
-            },
1927
-            {
1928
-                id: SIM_LAYER_2_RID,
1929
-                direction: 'recv'
1930
-            },
1931
-            {
1932
-                id: SIM_LAYER_3_RID,
1933
-                direction: 'recv'
1934
-            }
1935
-        ];
1936
-        // eslint-disable-next-line camelcase
1937
-        video.simulcast_03 = {
1938
-            value: `recv rid=${SIM_LAYER_RIDS.join(';')}`
1939
-        };
1940
-
1941
-        return new RTCSessionDescription({
1942
-            type: desc.type,
1943
-            sdp: transform.write(sdp)
1944
-        });
1945
-    };
1946
-
1947
 /**
1855
 /**
1948
  * Sets the max bitrate on the RTCRtpSender so that the
1856
  * Sets the max bitrate on the RTCRtpSender so that the
1949
  * bitrate of the enocder doesn't exceed the configured value.
1857
  * bitrate of the enocder doesn't exceed the configured value.
1955
 TraceablePeerConnection.prototype.setMaxBitRate = function(localTrack) {
1863
 TraceablePeerConnection.prototype.setMaxBitRate = function(localTrack) {
1956
     const mediaType = localTrack.type;
1864
     const mediaType = localTrack.type;
1957
 
1865
 
1958
-    if (!this.options.capScreenshareBitrate
1959
-        || mediaType === MediaType.AUDIO) {
1960
-
1866
+    // No need to set max bitrates on the streams in the following cases.
1867
+    // 1. When an audio track has been replaced.
1868
+    // 2. When a 'camera' track is replaced in plan-b mode, since its a new sender.
1869
+    // 3. When the config.js option for capping the SS bitrate is not enabled.
1870
+    if ((mediaType === MediaType.AUDIO)
1871
+        || (browser.usesPlanB() && !this.options.capScreenshareBitrate)
1872
+        || (browser.usesPlanB() && localTrack.videoType === 'camera')) {
1961
         return;
1873
         return;
1962
     }
1874
     }
1963
     if (!this.peerconnection.getSenders) {
1875
     if (!this.peerconnection.getSenders) {
1974
             try {
1886
             try {
1975
                 const parameters = sender.getParameters();
1887
                 const parameters = sender.getParameters();
1976
 
1888
 
1977
-                if (parameters.encodings && parameters.encodings.length) {
1978
-                    logger.info('Setting max bitrate on video stream');
1979
-                    for (const encoding in parameters.encodings) {
1980
-                        if (parameters.encodings.hasOwnProperty(encoding)) {
1981
-                            parameters.encodings[encoding].maxBitrate
1982
-                                = videoType === 'desktop'
1983
-                                    ? DESKSTOP_SHARE_RATE
1984
-                                    : SIM_LAYER_BITRATES_BPS[encoding];
1985
-                        }
1889
+                if (!parameters.encodings || !parameters.encodings.length) {
1890
+                    return;
1891
+                }
1892
+                logger.info('Setting max bitrate on video stream');
1893
+                for (const encoding in parameters.encodings) {
1894
+                    if (parameters.encodings.hasOwnProperty(encoding)) {
1895
+                        parameters.encodings[encoding].maxBitrate
1896
+                            = videoType === 'desktop' && browser.usesPlanB()
1897
+                                ? DESKSTOP_SHARE_RATE
1898
+
1899
+                                // In unified plan, simulcast for SS is on by default.
1900
+                                // When simulcast is disabled through a config.js option,
1901
+                                // we cap the bitrate on desktop and camera tracks to 2500 Kbps.
1902
+                                : this.isSimulcastOn()
1903
+                                    ? this.tpcUtils.simulcastEncodings[encoding].maxBitrate
1904
+                                    : MAX_BITRATE;
1986
                     }
1905
                     }
1987
-                    sender.setParameters(parameters);
1988
                 }
1906
                 }
1907
+                sender.setParameters(parameters);
1989
             } catch (err) {
1908
             } catch (err) {
1990
                 logger.error('Browser does not support getParameters/setParamters '
1909
                 logger.error('Browser does not support getParameters/setParamters '
1991
                     + 'or setting max bitrate on the encodings: ', err);
1910
                     + 'or setting max bitrate on the encodings: ', err);
2036
 
1955
 
2037
         if (this.isSimulcastOn()) {
1956
         if (this.isSimulcastOn()) {
2038
             // eslint-disable-next-line no-param-reassign
1957
             // eslint-disable-next-line no-param-reassign
2039
-            description = this._insertUnifiedPlanSimulcastReceive(description);
1958
+            description = this.tpcUtils._insertUnifiedPlanSimulcastReceive(description);
2040
             this.trace(
1959
             this.trace(
2041
                 'setRemoteDescription::postTransform (sim receive)',
1960
                 'setRemoteDescription::postTransform (sim receive)',
2042
                 dumpSDP(description));
1961
                 dumpSDP(description));
2164
  */
2083
  */
2165
 TraceablePeerConnection.prototype.setVideoTransferActive = function(active) {
2084
 TraceablePeerConnection.prototype.setVideoTransferActive = function(active) {
2166
     logger.debug(`${this} video transfer active: ${active}`);
2085
     logger.debug(`${this} video transfer active: ${active}`);
2086
+    if (browser.usesUnifiedPlan()) {
2087
+        return this.tpcUtils.setVideoTransferActive(active);
2088
+    }
2167
     const changed = this.videoTransferActive !== active;
2089
     const changed = this.videoTransferActive !== active;
2168
 
2090
 
2169
     this.videoTransferActive = active;
2091
     this.videoTransferActive = active;
2361
             = this.peerconnection.getSenders().find(sender =>
2283
             = this.peerconnection.getSenders().find(sender =>
2362
                 sender.track !== null && sender.track.kind === 'video');
2284
                 sender.track !== null && sender.track.kind === 'video');
2363
         const simParams = {
2285
         const simParams = {
2364
-            encodings: [
2365
-                {
2366
-                    rid: SIM_LAYER_1_RID,
2367
-                    scaleResolutionDownBy: 4
2368
-                },
2369
-                {
2370
-                    rid: SIM_LAYER_2_RID,
2371
-                    scaleResolutionDownBy: 2
2372
-                },
2373
-                {
2374
-                    rid: SIM_LAYER_3_RID
2375
-                }
2376
-            ]
2286
+            encodings: this.tpcUtils.simulcastEncodings
2377
         };
2287
         };
2378
 
2288
 
2379
         videoSender.setParameters(simParams);
2289
         videoSender.setParameters(simParams);
2458
             // configure simulcast for camera tracks always and for
2368
             // configure simulcast for camera tracks always and for
2459
             // desktop tracks only when the testing flag for maxbitrates
2369
             // desktop tracks only when the testing flag for maxbitrates
2460
             // in config.js is disabled.
2370
             // in config.js is disabled.
2461
-            if (this.isSimulcastOn()
2371
+            if (this.isSimulcastOn() && browser.usesSdpMungingForSimulcast()
2462
                 && (!this.options.capScreenshareBitrate
2372
                 && (!this.options.capScreenshareBitrate
2463
                 || (this.options.capScreenshareBitrate && hasCameraTrack(this)))) {
2373
                 || (this.options.capScreenshareBitrate && hasCameraTrack(this)))) {
2464
                 // eslint-disable-next-line no-param-reassign
2374
                 // eslint-disable-next-line no-param-reassign
2546
  * @param {TrackSSRCInfo} ssrcObj
2456
  * @param {TrackSSRCInfo} ssrcObj
2547
  * @return {number|null} the primary SSRC or <tt>null</tt>
2457
  * @return {number|null} the primary SSRC or <tt>null</tt>
2548
  */
2458
  */
2549
-function extractPrimarySSRC(ssrcObj) {
2459
+TraceablePeerConnection.prototype._extractPrimarySSRC = function(ssrcObj) {
2550
     if (ssrcObj && ssrcObj.groups && ssrcObj.groups.length) {
2460
     if (ssrcObj && ssrcObj.groups && ssrcObj.groups.length) {
2551
         return ssrcObj.groups[0].ssrcs[0];
2461
         return ssrcObj.groups[0].ssrcs[0];
2552
     } else if (ssrcObj && ssrcObj.ssrcs && ssrcObj.ssrcs.length) {
2462
     } else if (ssrcObj && ssrcObj.ssrcs && ssrcObj.ssrcs.length) {
2554
     }
2464
     }
2555
 
2465
 
2556
     return null;
2466
     return null;
2557
-}
2467
+};
2558
 
2468
 
2559
 /**
2469
 /**
2560
  * Goes over the SSRC map extracted from the latest local description and tries
2470
  * Goes over the SSRC map extracted from the latest local description and tries
2576
                 return;
2486
                 return;
2577
             }
2487
             }
2578
             const oldSSRC = this.localSSRCs.get(track.rtcId);
2488
             const oldSSRC = this.localSSRCs.get(track.rtcId);
2579
-            const newSSRCNum = extractPrimarySSRC(newSSRC);
2580
-            const oldSSRCNum = extractPrimarySSRC(oldSSRC);
2489
+            const newSSRCNum = this._extractPrimarySSRC(newSSRC);
2490
+            const oldSSRCNum = this._extractPrimarySSRC(oldSSRC);
2581
 
2491
 
2582
             // eslint-disable-next-line no-negated-condition
2492
             // eslint-disable-next-line no-negated-condition
2583
             if (newSSRCNum !== oldSSRCNum) {
2493
             if (newSSRCNum !== oldSSRCNum) {
2672
             ssrcs: [],
2582
             ssrcs: [],
2673
             groups: []
2583
             groups: []
2674
         };
2584
         };
2675
-        for (let i = 0; i < SIMULCAST_LAYERS; i++) {
2585
+        for (let i = 0; i < SIM_LAYER_RIDS.length; i++) {
2676
             ssrcInfo.ssrcs.push(SDPUtil.generateSsrc());
2586
             ssrcInfo.ssrcs.push(SDPUtil.generateSsrc());
2677
         }
2587
         }
2678
         ssrcInfo.groups.push({
2588
         ssrcInfo.groups.push({

+ 101
- 70
modules/xmpp/JingleSessionPC.js 查看文件

8
 import { $iq, Strophe } from 'strophe.js';
8
 import { $iq, Strophe } from 'strophe.js';
9
 import { integerHash } from '../util/StringUtils';
9
 import { integerHash } from '../util/StringUtils';
10
 
10
 
11
+import browser from './../browser';
11
 import JingleSession from './JingleSession';
12
 import JingleSession from './JingleSession';
12
 import * as JingleSessionState from './JingleSessionState';
13
 import * as JingleSessionState from './JingleSessionState';
13
 import SDP from './SDP';
14
 import SDP from './SDP';
510
                 break;
511
                 break;
511
             }
512
             }
512
         };
513
         };
514
+
515
+        /**
516
+         * The negotiationneeded event is fired whenever we shake the media on the
517
+         * RTCPeerConnection object.
518
+         */
513
         this.peerconnection.onnegotiationneeded = () => {
519
         this.peerconnection.onnegotiationneeded = () => {
520
+            const state = this.peerconnection.signalingState;
521
+            const remoteDescription = this.peerconnection.remoteDescription;
522
+
514
             this.room.eventEmitter.emit(XMPPEvents.PEERCONNECTION_READY, this);
523
             this.room.eventEmitter.emit(XMPPEvents.PEERCONNECTION_READY, this);
524
+            if (browser.usesUnifiedPlan() && state.toString() === 'stable'
525
+                && remoteDescription && typeof remoteDescription.sdp === 'string') {
526
+                logger.debug(`onnegotiationneeded fired on ${this.peerconnection} in state: ${state}`);
527
+                const workFunction = finishedCallback => {
528
+                    const oldSdp = new SDP(this.peerconnection.localDescription.sdp);
529
+
530
+                    this._renegotiate()
531
+                        .then(() => {
532
+                            const newSdp = new SDP(this.peerconnection.localDescription.sdp);
533
+
534
+                            this.notifyMySSRCUpdate(oldSdp, newSdp);
535
+                            finishedCallback();
536
+                        })
537
+                        .catch(() => {
538
+                            finishedCallback();
539
+                        });
540
+                };
541
+
542
+                this.modificationQueue.push(
543
+                    workFunction,
544
+                    error => {
545
+                        if (error) {
546
+                            logger.error('onnegotiationneeded error', error);
547
+                        } else {
548
+                            logger.debug('onnegotiationneeded executed - OK');
549
+                        }
550
+                    });
551
+            }
515
         };
552
         };
516
 
553
 
517
         // The signaling layer will bind it's listeners at this point
554
         // The signaling layer will bind it's listeners at this point
861
         }
898
         }
862
         const workFunction = finishedCallback => {
899
         const workFunction = finishedCallback => {
863
             for (const localTrack of localTracks) {
900
             for (const localTrack of localTracks) {
864
-                this.peerconnection.addTrack(localTrack);
901
+                this.peerconnection.addTrack(localTrack, true /* isInitiator */);
865
             }
902
             }
866
             this.peerconnection.createOffer(this.mediaConstraints)
903
             this.peerconnection.createOffer(this.mediaConstraints)
867
                 .then(offerSdp => {
904
                 .then(offerSdp => {
1705
             });
1742
             });
1706
     }
1743
     }
1707
 
1744
 
1708
-    /**
1709
-     * Replaces the existing mediaStream on the underlying peerconnection with the newly
1710
-     * added stream on the same JitsiLocalTrack wihtout the need to perform a offer/answer
1711
-     * cycle.
1712
-     * @param {JitsiLocalTrack} track - the current track in use whose media stream has been
1713
-     * updated.
1714
-     * @returns {Promise} which resolves once the replacement is complete or reject with an
1715
-     * error {string}.
1716
-     */
1717
-    replaceTrackWithoutOfferAnswer(track) {
1718
-        return this.peerconnection.replaceTrackWithoutOfferAnswer(track);
1719
-    }
1720
-
1721
     /**
1745
     /**
1722
      * Replaces <tt>oldTrack</tt> with <tt>newTrack</tt> and performs a single
1746
      * Replaces <tt>oldTrack</tt> with <tt>newTrack</tt> and performs a single
1723
      * offer/answer cycle after both operations are done. Either
1747
      * offer/answer cycle after both operations are done. Either
1734
         const workFunction = finishedCallback => {
1758
         const workFunction = finishedCallback => {
1735
             const oldLocalSdp = this.peerconnection.localDescription.sdp;
1759
             const oldLocalSdp = this.peerconnection.localDescription.sdp;
1736
 
1760
 
1737
-            // NOTE the code below assumes that no more than 1 video track
1738
-            // can be added to the peer connection.
1739
-            // Transition from camera to desktop share
1740
-            // or transition from one camera source to another.
1741
-            if (this.peerconnection.options.capScreenshareBitrate
1742
-                && oldTrack && newTrack && newTrack.isVideoTrack()) {
1743
-                // Clearing current primary SSRC will make
1744
-                // the SdpConsistency generate a new one which will result
1745
-                // with:
1746
-                // 1. source-remove for the old video stream.
1747
-                // 2. source-add for the new video stream.
1748
-                this.peerconnection.clearRecvonlySsrc();
1749
-            }
1761
+            if (browser.usesPlanB()) {
1762
+                // NOTE the code below assumes that no more than 1 video track
1763
+                // can be added to the peer connection.
1764
+                // Transition from camera to desktop share
1765
+                // or transition from one camera source to another.
1766
+                if (this.peerconnection.options.capScreenshareBitrate
1767
+                    && oldTrack && newTrack && newTrack.isVideoTrack()) {
1768
+                    // Clearing current primary SSRC will make
1769
+                    // the SdpConsistency generate a new one which will result
1770
+                    // with:
1771
+                    // 1. source-remove for the old video stream.
1772
+                    // 2. source-add for the new video stream.
1773
+                    this.peerconnection.clearRecvonlySsrc();
1774
+                }
1750
 
1775
 
1751
-            // Transition from no video to video (unmute).
1752
-            if (!oldTrack && newTrack && newTrack.isVideoTrack()) {
1753
-                // Clearing current primary SSRC will make
1754
-                // the SdpConsistency generate a new one which will result
1755
-                // with:
1756
-                // 1. source-remove for the recvonly
1757
-                // 2. source-add for the new video stream
1758
-                this.peerconnection.clearRecvonlySsrc();
1759
-
1760
-            // Transition from video to no video
1761
-            } else if (oldTrack && oldTrack.isVideoTrack() && !newTrack) {
1762
-                // Clearing current primary SSRC and generating the recvonly
1763
-                // will result in:
1764
-                // 1. source-remove for the old video stream
1765
-                // 2. source-add for the recvonly stream
1766
-                this.peerconnection.clearRecvonlySsrc();
1767
-                this.peerconnection.generateRecvonlySsrc();
1776
+                // Transition from no video to video (unmute).
1777
+                if (!oldTrack && newTrack && newTrack.isVideoTrack()) {
1778
+                    // Clearing current primary SSRC will make
1779
+                    // the SdpConsistency generate a new one which will result
1780
+                    // with:
1781
+                    // 1. source-remove for the recvonly
1782
+                    // 2. source-add for the new video stream
1783
+                    this.peerconnection.clearRecvonlySsrc();
1784
+
1785
+                // Transition from video to no video
1786
+                } else if (oldTrack && oldTrack.isVideoTrack() && !newTrack) {
1787
+                    // Clearing current primary SSRC and generating the recvonly
1788
+                    // will result in:
1789
+                    // 1. source-remove for the old video stream
1790
+                    // 2. source-add for the recvonly stream
1791
+                    this.peerconnection.clearRecvonlySsrc();
1792
+                    this.peerconnection.generateRecvonlySsrc();
1793
+                }
1768
             }
1794
             }
1769
 
1795
 
1770
             this.peerconnection.replaceTrack(oldTrack, newTrack)
1796
             this.peerconnection.replaceTrack(oldTrack, newTrack)
1771
-            .then(shouldRenegotiate => {
1772
-                if (shouldRenegotiate
1773
-                    && (oldTrack || newTrack)
1774
-                    && this.state === JingleSessionState.ACTIVE) {
1775
-                    this._renegotiate()
1776
-                        .then(() => {
1777
-                            const newLocalSDP
1778
-                                = new SDP(
1779
-                                    this.peerconnection.localDescription.sdp);
1780
-
1781
-                            this.notifyMySSRCUpdate(
1782
-                                new SDP(oldLocalSdp), newLocalSDP);
1783
-
1784
-                            // configure max bitrate only when media is routed
1785
-                            // through JVB. For p2p case, browser takes care of
1786
-                            // adjusting the uplink based on the feedback it
1787
-                            // gets from the peer.
1788
-                            if (newTrack && !this.isP2P) {
1789
-                                this.peerconnection.setMaxBitRate(newTrack);
1790
-                            }
1791
-                            finishedCallback();
1797
+                .then(shouldRenegotiate => {
1798
+                    let promise = Promise.resolve();
1799
+
1800
+                    if (shouldRenegotiate
1801
+                        && (oldTrack || newTrack)
1802
+                        && this.state === JingleSessionState.ACTIVE) {
1803
+                        promise = this._renegotiate().then(() => {
1804
+                            const newLocalSDP = new SDP(this.peerconnection.localDescription.sdp);
1805
+
1806
+                            this.notifyMySSRCUpdate(new SDP(oldLocalSdp), newLocalSDP);
1792
                         },
1807
                         },
1793
                         finishedCallback /* will be called with en error */);
1808
                         finishedCallback /* will be called with en error */);
1794
-                } else {
1795
-                    finishedCallback();
1796
-                }
1797
-            });
1809
+                    }
1810
+
1811
+                    // Wait for the renegotation to be done if needed (plan-b) before adjusting
1812
+                    // the max bitrates on the video sender.
1813
+                    promise.then(() => {
1814
+                        // configure max bitrate only when media is routed
1815
+                        // through JVB. For p2p case, browser takes care of
1816
+                        // adjusting the uplink based on the feedback it
1817
+                        // gets from the peer.
1818
+                        if (newTrack && !this.isP2P) {
1819
+                            this.peerconnection.setMaxBitRate(newTrack);
1820
+                        }
1821
+                        finishedCallback();
1822
+                    }, finishedCallback /* will be called with en error */);
1823
+                })
1824
+                .catch(err => {
1825
+                    finishedCallback(err);
1826
+                });
1798
         };
1827
         };
1799
 
1828
 
1800
         return new Promise((resolve, reject) => {
1829
         return new Promise((resolve, reject) => {
1981
 
2010
 
1982
             if (!tpcOperation()) {
2011
             if (!tpcOperation()) {
1983
                 finishedCallback(`${operationName} failed!`);
2012
                 finishedCallback(`${operationName} failed!`);
1984
-            } else if (!oldLocalSDP || !tpc.remoteDescription.sdp) {
2013
+
2014
+            // Do not renegotiate when browser is running in Unified-plan mode.
2015
+            } else if (!oldLocalSDP || !tpc.remoteDescription.sdp || browser.usesUnifiedPlan()) {
1985
                 finishedCallback();
2016
                 finishedCallback();
1986
             } else {
2017
             } else {
1987
                 this._renegotiate()
2018
                 this._renegotiate()

正在加载...
取消
保存