Pārlūkot izejas kodu

ref(TPC) Move all the utility functions to TPCUtils.

release-8443
Jaya Allamsetty 1 gadu atpakaļ
vecāks
revīzija
ad5559ab3f

+ 340
- 232
modules/RTC/TPCUtils.js Parādīt failu

5
 import { CodecMimeType } from '../../service/RTC/CodecMimeType';
5
 import { CodecMimeType } from '../../service/RTC/CodecMimeType';
6
 import { MediaDirection } from '../../service/RTC/MediaDirection';
6
 import { MediaDirection } from '../../service/RTC/MediaDirection';
7
 import { MediaType } from '../../service/RTC/MediaType';
7
 import { MediaType } from '../../service/RTC/MediaType';
8
-import { getSourceIndexFromSourceName } from '../../service/RTC/SignalingLayer';
9
 import {
8
 import {
10
     SIM_LAYERS,
9
     SIM_LAYERS,
11
     STANDARD_CODEC_SETTINGS,
10
     STANDARD_CODEC_SETTINGS,
15
 import { VideoEncoderScalabilityMode } from '../../service/RTC/VideoEncoderScalabilityMode';
14
 import { VideoEncoderScalabilityMode } from '../../service/RTC/VideoEncoderScalabilityMode';
16
 import { VideoType } from '../../service/RTC/VideoType';
15
 import { VideoType } from '../../service/RTC/VideoType';
17
 import browser from '../browser';
16
 import browser from '../browser';
17
+import SDPUtil from '../sdp/SDPUtil';
18
 
18
 
19
 const logger = getLogger(__filename);
19
 const logger = getLogger(__filename);
20
+const DD_HEADER_EXT_URI
21
+    = 'https://aomediacodec.github.io/av1-rtp-spec/#dependency-descriptor-rtp-header-extension';
22
+const DD_HEADER_EXT_ID = 11;
20
 const VIDEO_CODECS = [ CodecMimeType.AV1, CodecMimeType.H264, CodecMimeType.VP8, CodecMimeType.VP9 ];
23
 const VIDEO_CODECS = [ CodecMimeType.AV1, CodecMimeType.H264, CodecMimeType.VP8, CodecMimeType.VP9 ];
21
 
24
 
22
 /**
25
 /**
23
- * Handles track related operations on TraceablePeerConnection when browser is
24
- * running in unified plan mode.
26
+ * Handles all the utility functions for the TraceablePeerConnection class, like calculating the encoding parameters,
27
+ * determining the media direction, calculating bitrates based on the current codec settings, etc.
25
  */
28
  */
26
 export class TPCUtils {
29
 export class TPCUtils {
27
     /**
30
     /**
29
      *
32
      *
30
      * @param peerconnection - the tpc instance for which we have utility functions.
33
      * @param peerconnection - the tpc instance for which we have utility functions.
31
      */
34
      */
32
-    constructor(peerconnection) {
35
+    constructor(peerconnection, options) {
33
         this.pc = peerconnection;
36
         this.pc = peerconnection;
37
+        this.options = options;
34
         this.codecSettings = cloneDeep(STANDARD_CODEC_SETTINGS);
38
         this.codecSettings = cloneDeep(STANDARD_CODEC_SETTINGS);
35
-        const videoQualitySettings = this.pc.options?.videoQuality;
39
+
40
+        /**
41
+         * Flag indicating bridge support for AV1 codec. On the bridge connection, it is supported only when support for
42
+         * Dependency Descriptor header extensions is offered by Jicofo. H.264 simulcast is also possible when these
43
+         * header extensions are negotiated.
44
+         */
45
+        this.supportsDDHeaderExt = false;
46
+
47
+        /**
48
+         * Reads videoQuality settings from config.js and overrides the code defaults for video codecs.
49
+         */
50
+        const videoQualitySettings = this.options.videoQuality;
36
 
51
 
37
         if (videoQualitySettings) {
52
         if (videoQualitySettings) {
38
             for (const codec of VIDEO_CODECS) {
53
             for (const codec of VIDEO_CODECS) {
86
         const codecBitrates = this.codecSettings[codec].maxBitratesVideo;
101
         const codecBitrates = this.codecSettings[codec].maxBitratesVideo;
87
         const trackCaptureHeight = localVideoTrack.getCaptureResolution();
102
         const trackCaptureHeight = localVideoTrack.getCaptureResolution();
88
         const effectiveNewHeight = newHeight > trackCaptureHeight ? trackCaptureHeight : newHeight;
103
         const effectiveNewHeight = newHeight > trackCaptureHeight ? trackCaptureHeight : newHeight;
89
-        const desktopShareBitrate = this.pc.options?.videoQuality?.desktopbitrate || codecBitrates.ssHigh;
104
+        const desktopShareBitrate = this.options.videoQuality?.desktopbitrate || codecBitrates.ssHigh;
90
         const isScreenshare = localVideoTrack.getVideoType() === VideoType.DESKTOP;
105
         const isScreenshare = localVideoTrack.getVideoType() === VideoType.DESKTOP;
91
         let scalabilityMode = this.codecSettings[codec].useKSVC
106
         let scalabilityMode = this.codecSettings[codec].useKSVC
92
             ? VideoEncoderScalabilityMode.L3T3_KEY : VideoEncoderScalabilityMode.L3T3;
107
             ? VideoEncoderScalabilityMode.L3T3_KEY : VideoEncoderScalabilityMode.L3T3;
140
     }
155
     }
141
 
156
 
142
     /**
157
     /**
143
-     * Configures the RTCRtpEncodingParameters of the outbound rtp stream associated with the given track.
144
-     *
145
-     * @param {JitsiLocalTracj} localTrack - The local track whose outbound stream needs to be configured.
146
-     * @returns {Promise} - A promise that resolves when the operation is successful, rejected otherwise.
147
-     */
148
-    _configureSenderEncodings(localTrack) {
149
-        const mediaType = localTrack.getType();
150
-        const transceiver = localTrack?.track && localTrack.getOriginalStream()
151
-            ? this.pc.peerconnection.getTransceivers().find(t => t.sender?.track?.id === localTrack.getTrackId())
152
-            : this.pc.peerconnection.getTransceivers().find(t => t.receiver?.track?.kind === mediaType);
153
-        const parameters = transceiver?.sender?.getParameters();
154
-
155
-        // Resolve if the encodings are not available yet. This happens immediately after the track is added to the
156
-        // peerconnection on chrome in unified-plan. It is ok to ignore and not report the error here since the
157
-        // action that triggers 'addTrack' (like unmute) will also configure the encodings and set bitrates after that.
158
-        if (!parameters?.encodings?.length) {
159
-            return Promise.resolve();
160
-        }
161
-
162
-        parameters.encodings = this._getStreamEncodings(localTrack);
163
-
164
-        return transceiver.sender.setParameters(parameters);
165
-    }
166
-
167
-    /**
168
-     * Enables/disables the streams by changing the active field on RTCRtpEncodingParameters for a given RTCRtpSender.
169
-     *
170
-     * @param {RTCRtpSender} sender - the sender associated with a MediaStreamTrack.
171
-     * @param {boolean} enable - whether the streams needs to be enabled or disabled.
172
-     * @returns {Promise} - A promise that resolves when the operation is successful, rejected otherwise.
173
-     */
174
-    _enableSenderEncodings(sender, enable) {
175
-        const parameters = sender.getParameters();
176
-
177
-        if (parameters?.encodings?.length) {
178
-            for (const encoding of parameters.encodings) {
179
-                encoding.active = enable;
180
-            }
181
-        }
182
-
183
-        return sender.setParameters(parameters);
184
-    }
185
-
186
-    /**
187
-     * Obtains stream encodings that need to be configured on the given track based
188
-     * on the track media type and the simulcast setting.
189
-     * @param {JitsiLocalTrack} localTrack
190
-     */
191
-    _getStreamEncodings(localTrack) {
192
-        if (localTrack.isAudioTrack()) {
193
-            return [ { active: this.pc.audioTransferActive } ];
194
-        }
195
-        const codec = this.pc.getConfiguredVideoCodec(localTrack);
196
-
197
-        if (this.pc.isSpatialScalabilityOn()) {
198
-            return this._getVideoStreamEncodings(localTrack, codec);
199
-        }
200
-
201
-        return [ {
202
-            active: this.pc.videoTransferActive,
203
-            maxBitrate: this.codecSettings[codec].maxBitratesVideo.high
204
-        } ];
205
-    }
206
-
207
-    /**
208
-     * The startup configuration for the stream encodings that are applicable to
209
-     * the video stream when a new sender is created on the peerconnection. The initial
210
-     * config takes into account the differences in browser's simulcast implementation.
158
+     * The startup configuration for the stream encodings that are applicable to the video stream when a new sender is
159
+     * created on the peerconnection. The initial config takes into account the differences in browser's simulcast
160
+     * implementation.
211
      *
161
      *
212
      * Encoding parameters:
162
      * Encoding parameters:
213
      * active - determine the on/off state of a particular encoding.
163
      * active - determine the on/off state of a particular encoding.
214
-     * maxBitrate - max. bitrate value to be applied to that particular encoding
215
-     *  based on the encoding's resolution and config.js videoQuality settings if applicable.
164
+     * maxBitrate - max. bitrate value to be applied to that particular encoding based on the encoding's resolution and
165
+     *  config.js videoQuality settings if applicable.
216
      * rid - Rtp Stream ID that is configured for a particular simulcast stream.
166
      * rid - Rtp Stream ID that is configured for a particular simulcast stream.
217
-     * scaleResolutionDownBy - the factor by which the encoding is scaled down from the
218
-     *  original resolution of the captured video.
167
+     * scaleResolutionDownBy - the factor by which the encoding is scaled down from the original resolution of the
168
+     *  captured video.
219
      *
169
      *
220
      * @param {JitsiLocalTrack} localTrack
170
      * @param {JitsiLocalTrack} localTrack
221
      * @param {String} codec
171
      * @param {String} codec
339
             && !browser.isWebKitBased();
289
             && !browser.isWebKitBased();
340
     }
290
     }
341
 
291
 
342
-    /**
343
-     * Updates the sender parameters in the stream encodings.
344
-     *
345
-     * @param {RTCRtpSender} sender - the sender associated with a MediaStreamTrack.
346
-     * @param {boolean} enable - whether the streams needs to be enabled or disabled.
347
-     * @returns {Promise} - A promise that resolves when the operation is successful, rejected otherwise.
348
-     */
349
-    _updateSenderEncodings(sender, enable) {
350
-        const parameters = sender.getParameters();
351
-
352
-        if (parameters?.encodings?.length) {
353
-            for (const encoding of parameters.encodings) {
354
-                encoding.active = enable;
355
-            }
356
-        }
357
-
358
-        return sender.setParameters(parameters);
359
-    }
360
-
361
-    /**
362
-    * Adds {@link JitsiLocalTrack} to the WebRTC peerconnection for the first time.
363
-    *
364
-    * @param {JitsiLocalTrack} track - track to be added to the peerconnection.
365
-    * @param {boolean} isInitiator - boolean that indicates if the endpoint is offerer in a p2p connection.
366
-    * @returns {RTCRtpTransceiver} - the transceiver that the track was added to.
367
-    */
368
-    addTrack(localTrack, isInitiator) {
369
-        const track = localTrack.getTrack();
370
-        let transceiver;
371
-
372
-        if (isInitiator) {
373
-            const streams = [];
374
-
375
-            if (localTrack.getOriginalStream()) {
376
-                streams.push(localTrack.getOriginalStream());
377
-            }
378
-
379
-            // Use pc.addTransceiver() for the initiator case when local tracks are getting added
380
-            // to the peerconnection before a session-initiate is sent over to the peer.
381
-            const transceiverInit = {
382
-                direction: MediaDirection.SENDRECV,
383
-                streams,
384
-                sendEncodings: []
385
-            };
386
-
387
-            if (!browser.isFirefox()) {
388
-                transceiverInit.sendEncodings = this._getStreamEncodings(localTrack);
389
-            }
390
-            transceiver = this.pc.peerconnection.addTransceiver(track, transceiverInit);
391
-        } else {
392
-            // Use pc.addTrack() for responder case so that we can re-use the m-lines that were created
393
-            // when setRemoteDescription was called. pc.addTrack() automatically  attaches to any existing
394
-            // unused "recv-only" transceiver.
395
-            const sender = this.pc.peerconnection.addTrack(track);
396
-
397
-            // Find the corresponding transceiver that the track was attached to.
398
-            transceiver = this.pc.peerconnection.getTransceivers().find(t => t.sender === sender);
399
-        }
400
-
401
-        return transceiver;
402
-    }
403
-
404
     /**
292
     /**
405
      * Returns the calculated active state of the stream encodings based on the frame height requested for the send
293
      * Returns the calculated active state of the stream encodings based on the frame height requested for the send
406
      * stream. All the encodings that have a resolution lower than the frame height requested will be enabled.
294
      * stream. All the encodings that have a resolution lower than the frame height requested will be enabled.
465
      */
353
      */
466
     calculateEncodingsBitrates(localVideoTrack, codec, newHeight) {
354
     calculateEncodingsBitrates(localVideoTrack, codec, newHeight) {
467
         const codecBitrates = this.codecSettings[codec].maxBitratesVideo;
355
         const codecBitrates = this.codecSettings[codec].maxBitratesVideo;
468
-        const desktopShareBitrate = this.pc.options?.videoQuality?.desktopbitrate || codecBitrates.ssHigh;
356
+        const desktopShareBitrate = this.options.videoQuality?.desktopbitrate || codecBitrates.ssHigh;
469
         const encodingsBitrates = this._getVideoStreamEncodings(localVideoTrack, codec)
357
         const encodingsBitrates = this._getVideoStreamEncodings(localVideoTrack, codec)
470
         .map((encoding, idx) => {
358
         .map((encoding, idx) => {
471
             let bitrate = encoding.maxBitrate;
359
             let bitrate = encoding.maxBitrate;
588
         });
476
         });
589
     }
477
     }
590
 
478
 
479
+    /**
480
+     * Returns the codec that is configured on the client as the preferred video codec for the given local video track.
481
+     *
482
+     * @param {JitsiLocalTrack} localTrack - The local video track.
483
+     * @returns {CodecMimeType} The codec that is set as the preferred codec for the given local video track.
484
+     */
485
+    getConfiguredVideoCodec(localTrack) {
486
+        const localVideoTrack = localTrack ?? this.pc.getLocalVideoTracks()[0];
487
+        const rtpSender = this.pc.findSenderForTrack(localVideoTrack.getTrack());
488
+
489
+        if (this.pc.usesCodecSelectionAPI() && rtpSender) {
490
+            const { codecs } = rtpSender.getParameters();
491
+
492
+            return codecs[0].mimeType.split('/')[1].toLowerCase();
493
+        }
494
+
495
+        const sdp = this.pc.remoteDescription?.sdp;
496
+        const defaultCodec = CodecMimeType.VP8;
497
+
498
+        if (!sdp) {
499
+            return defaultCodec;
500
+        }
501
+        const parsedSdp = transform.parse(sdp);
502
+        const mLine = parsedSdp.media
503
+            .find(m => m.mid.toString() === this.pc.localTrackTransceiverMids.get(localVideoTrack.rtcId));
504
+        const payload = mLine.payloads.split(' ')[0];
505
+        const { codec } = mLine.rtp.find(rtp => rtp.payload === Number(payload));
506
+
507
+        if (codec) {
508
+            return Object.values(CodecMimeType).find(value => value === codec.toLowerCase());
509
+        }
510
+
511
+        return defaultCodec;
512
+    }
513
+
514
+    /**
515
+     * Returns the codecs in the current order of preference as configured on the peerconnection.
516
+     *
517
+     * @param {RTCSessionDescription} - The local description to be used.
518
+     * @returns {Array}
519
+     */
520
+    getConfiguredVideoCodecs(description) {
521
+        const currentSdp = description?.sdp ?? this.pc.localDescription?.sdp;
522
+
523
+        if (!currentSdp) {
524
+            return [];
525
+        }
526
+        const parsedSdp = transform.parse(currentSdp);
527
+        const mLine = parsedSdp.media.find(m => m.type === MediaType.VIDEO);
528
+        const codecs = new Set(mLine.rtp
529
+            .filter(pt => pt.codec.toLowerCase() !== 'rtx')
530
+            .map(pt => pt.codec.toLowerCase()));
531
+
532
+        return Array.from(codecs);
533
+    }
534
+
535
+    /**
536
+     * Returns the desired media direction for the given media type based on the current state of the peerconnection.
537
+     *
538
+     * @param {MediaType} mediaType - The media type for which the desired media direction is to be obtained.
539
+     * @param {boolean} isAddOperation - Whether the direction is being set for a source add operation.
540
+     * @returns {MediaDirection} - The desired media direction for the given media type.
541
+     */
542
+    getDesiredMediaDirection(mediaType, isAddOperation = false) {
543
+        const hasLocalSource = this.pc.getLocalTracks(mediaType).length > 0;
544
+
545
+        if (isAddOperation) {
546
+            return hasLocalSource ? MediaDirection.SENDRECV : MediaDirection.SENDONLY;
547
+        }
548
+
549
+        return hasLocalSource ? MediaDirection.RECVONLY : MediaDirection.INACTIVE;
550
+    }
551
+
552
+    /**
553
+     * Obtains stream encodings that need to be configured on the given track based
554
+     * on the track media type and the simulcast setting.
555
+     * @param {JitsiLocalTrack} localTrack
556
+     */
557
+    getStreamEncodings(localTrack) {
558
+        if (localTrack.isAudioTrack()) {
559
+            return [ { active: this.pc.audioTransferActive } ];
560
+        }
561
+        const codec = this.getConfiguredVideoCodec(localTrack);
562
+
563
+        if (this.pc.isSpatialScalabilityOn()) {
564
+            return this._getVideoStreamEncodings(localTrack, codec);
565
+        }
566
+
567
+        return [ {
568
+            active: this.pc.videoTransferActive,
569
+            maxBitrate: this.codecSettings[codec].maxBitratesVideo.high
570
+        } ];
571
+    }
572
+
591
     /**
573
     /**
592
      * Takes in a *unified plan* offer and inserts the appropriate parameters for adding simulcast receive support.
574
      * Takes in a *unified plan* offer and inserts the appropriate parameters for adding simulcast receive support.
593
      * @param {Object} desc - A session description object
575
      * @param {Object} desc - A session description object
622
         const simulcastLine = `recv ${ridLine}`;
604
         const simulcastLine = `recv ${ridLine}`;
623
         const sdp = transform.parse(desc.sdp);
605
         const sdp = transform.parse(desc.sdp);
624
         const mLines = sdp.media.filter(m => m.type === MediaType.VIDEO);
606
         const mLines = sdp.media.filter(m => m.type === MediaType.VIDEO);
625
-        const senderMids = Array.from(this.pc._localTrackTransceiverMids.values());
607
+        const senderMids = Array.from(this.pc.localTrackTransceiverMids.values());
626
 
608
 
627
         mLines.forEach((mLine, idx) => {
609
         mLines.forEach((mLine, idx) => {
628
             // Make sure the simulcast recv line is only set on video descriptions that are associated with senders.
610
             // Make sure the simulcast recv line is only set on video descriptions that are associated with senders.
671
     }
653
     }
672
 
654
 
673
     /**
655
     /**
674
-     * Replaces the existing track on a RTCRtpSender with the given track.
656
+     * Munges the session description to ensure that the codec order is as per the preferred codec settings.
675
      *
657
      *
676
-     * @param {JitsiLocalTrack} oldTrack - existing track on the sender that needs to be removed.
677
-     * @param {JitsiLocalTrack} newTrack - new track that needs to be added to the sender.
678
-     * @returns {Promise<RTCRtpTransceiver>} - resolved with the associated transceiver when done, rejected otherwise.
658
+     * @param {RTCSessionDescription} description - the local/remote description to be munged.
659
+     * @returns {RTCSessionDescription} - the munged local/remote description.
679
      */
660
      */
680
-    replaceTrack(oldTrack, newTrack) {
681
-        const mediaType = newTrack?.getType() ?? oldTrack?.getType();
682
-        const localTracks = this.pc.getLocalTracks(mediaType);
683
-        const track = newTrack?.getTrack() ?? null;
684
-        const isNewLocalSource = localTracks?.length
685
-            && !oldTrack
686
-            && newTrack
687
-            && !localTracks.find(t => t === newTrack);
688
-        let transceiver;
689
-
690
-        // If old track exists, replace the track on the corresponding sender.
691
-        if (oldTrack && !oldTrack.isMuted()) {
692
-            transceiver = this.pc.peerconnection.getTransceivers().find(t => t.sender.track === oldTrack.getTrack());
693
-
694
-        // Find the first recvonly transceiver when more than one track of the same media type is being added to the pc.
695
-        // As part of the track addition, a new m-line was added to the remote description with direction set to
696
-        // recvonly.
697
-        } else if (isNewLocalSource) {
698
-            transceiver = this.pc.peerconnection.getTransceivers().find(
699
-                t => t.receiver.track.kind === mediaType
700
-                && t.direction === MediaDirection.RECVONLY
701
-
702
-                // Re-use any existing recvonly transceiver (if available) for p2p case.
703
-                && ((this.pc.isP2P && t.currentDirection === MediaDirection.RECVONLY)
704
-                    || (t.currentDirection === MediaDirection.INACTIVE && !t.stopped)));
705
-
706
-        // For mute/unmute operations, find the transceiver based on the track index in the source name if present,
707
-        // otherwise it is assumed to be the first local track that was added to the peerconnection.
708
-        } else {
709
-            transceiver = this.pc.peerconnection.getTransceivers().find(t => t.receiver.track.kind === mediaType);
710
-            const sourceName = newTrack?.getSourceName() ?? oldTrack?.getSourceName();
711
-
712
-            if (sourceName) {
713
-                const trackIndex = getSourceIndexFromSourceName(sourceName);
714
-
715
-                if (this.pc.isP2P) {
716
-                    transceiver = this.pc.peerconnection.getTransceivers()
717
-                        .filter(t => t.receiver.track.kind === mediaType)[trackIndex];
718
-                } else if (oldTrack) {
719
-                    const transceiverMid = this.pc._localTrackTransceiverMids.get(oldTrack.rtcId);
720
-
721
-                    transceiver = this.pc.peerconnection.getTransceivers().find(t => t.mid === transceiverMid);
722
-                } else if (trackIndex) {
723
-                    transceiver = this.pc.peerconnection.getTransceivers()
724
-                            .filter(t => t.receiver.track.kind === mediaType
725
-                                && t.direction !== MediaDirection.RECVONLY)[trackIndex];
661
+    mungeCodecOrder(description) {
662
+        const codecSettings = this.pc.codecSettings;
663
+
664
+        if (!codecSettings) {
665
+            return description;
666
+        }
667
+
668
+        const { isP2P } = this.options;
669
+        const parsedSdp = transform.parse(description.sdp);
670
+        const mLines = parsedSdp.media.filter(m => m.type === codecSettings.mediaType);
671
+
672
+        if (!mLines.length) {
673
+            return description;
674
+        }
675
+
676
+        for (const mLine of mLines) {
677
+            const currentCodecs = this.getConfiguredVideoCodecs(description);
678
+
679
+            for (const codec of currentCodecs) {
680
+                if (isP2P) {
681
+                    // 1. Strip the high profile H264 codecs on all clients. macOS started offering encoder for H.264
682
+                    //   level 5.2 but a decoder only for level 3.1. Therfore, strip all main and high level codecs for
683
+                    //   H.264.
684
+                    // 2. There are multiple VP9 payload types generated by the browser, more payload types are added
685
+                    //   if the endpoint doesn't have a local video source. Therefore, strip all the high profile codec
686
+                    //   variants for VP9 so that only one payload type for VP9 is negotiated between the peers.
687
+                    if (codec === CodecMimeType.H264 || codec === CodecMimeType.VP9) {
688
+                        SDPUtil.stripCodec(mLine, codec, true /* high profile */);
689
+                    }
690
+
691
+                    // Do not negotiate ULPFEC and RED either.
692
+                    if (codec === CodecMimeType.ULPFEC || codec === CodecMimeType.RED) {
693
+                        SDPUtil.stripCodec(mLine, codec, false);
694
+                    }
695
+                }
696
+            }
697
+
698
+            // Reorder the codecs based on the preferred settings.
699
+            if (!this.pc.usesCodecSelectionAPI()) {
700
+                for (const codec of codecSettings.codecList.slice().reverse()) {
701
+                    SDPUtil.preferCodec(mLine, codec, isP2P);
726
                 }
702
                 }
727
             }
703
             }
728
         }
704
         }
729
-        if (!transceiver) {
730
-            return Promise.reject(
731
-                new Error(`Replace track failed - no transceiver for old: ${oldTrack}, new: ${newTrack}`));
732
-        }
733
-        logger.debug(`${this.pc} Replacing ${oldTrack} with ${newTrack}`);
734
 
705
 
735
-        return transceiver.sender.replaceTrack(track)
736
-            .then(() => Promise.resolve(transceiver));
706
+        return new RTCSessionDescription({
707
+            type: description.type,
708
+            sdp: transform.write(parsedSdp)
709
+        });
737
     }
710
     }
738
 
711
 
739
     /**
712
     /**
740
-     * Set the simulcast stream encoding properties on the RTCRtpSender.
713
+     * Munges the stereo flag as well as the opusMaxAverageBitrate in the SDP, based on values set through config.js,
714
+     * if present.
741
      *
715
      *
742
-     * @param {JitsiLocalTrack} localTrack - the current track in use for which the encodings are to be set.
743
-     * @returns {Promise<void>} - resolved when done.
716
+     * @param {RTCSessionDescription} description that needs to be munged.
717
+     * @returns {RTCSessionDescription} the munged description.
744
      */
718
      */
745
-    setEncodings(localTrack) {
746
-        if (localTrack.getType() === MediaType.VIDEO) {
747
-            return this.pc._updateVideoSenderParameters(() => this._configureSenderEncodings(localTrack));
719
+    mungeOpus(description) {
720
+        const { audioQuality } = this.options;
721
+
722
+        if (!audioQuality?.enableOpusDtx && !audioQuality?.stereo && !audioQuality?.opusMaxAverageBitrate) {
723
+            return description;
748
         }
724
         }
749
 
725
 
750
-        return this._configureSenderEncodings(localTrack);
726
+        const parsedSdp = transform.parse(description.sdp);
727
+        const mLines = parsedSdp.media.filter(m => m.type === MediaType.AUDIO);
728
+
729
+        for (const mLine of mLines) {
730
+            const { payload } = mLine.rtp.find(protocol => protocol.codec === CodecMimeType.OPUS);
731
+
732
+            if (!payload) {
733
+                // eslint-disable-next-line no-continue
734
+                continue;
735
+            }
736
+
737
+            let fmtpOpus = mLine.fmtp.find(protocol => protocol.payload === payload);
738
+
739
+            if (!fmtpOpus) {
740
+                fmtpOpus = {
741
+                    payload,
742
+                    config: ''
743
+                };
744
+            }
745
+
746
+            const fmtpConfig = transform.parseParams(fmtpOpus.config);
747
+            let sdpChanged = false;
748
+
749
+            if (audioQuality?.stereo) {
750
+                fmtpConfig.stereo = 1;
751
+                sdpChanged = true;
752
+            }
753
+
754
+            if (audioQuality?.opusMaxAverageBitrate) {
755
+                fmtpConfig.maxaveragebitrate = audioQuality.opusMaxAverageBitrate;
756
+                sdpChanged = true;
757
+            }
758
+
759
+            // On Firefox, the OpusDtx enablement has no effect
760
+            if (!browser.isFirefox() && audioQuality?.enableOpusDtx) {
761
+                fmtpConfig.usedtx = 1;
762
+                sdpChanged = true;
763
+            }
764
+
765
+            if (!sdpChanged) {
766
+                // eslint-disable-next-line no-continue
767
+                continue;
768
+            }
769
+
770
+            let mungedConfig = '';
771
+
772
+            for (const key of Object.keys(fmtpConfig)) {
773
+                mungedConfig += `${key}=${fmtpConfig[key]}; `;
774
+            }
775
+
776
+            fmtpOpus.config = mungedConfig.trim();
777
+        }
778
+
779
+        return new RTCSessionDescription({
780
+            type: description.type,
781
+            sdp: transform.write(parsedSdp)
782
+        });
751
     }
783
     }
752
 
784
 
753
     /**
785
     /**
754
-     * Resumes or suspends media on the peerconnection by setting the active state on RTCRtpEncodingParameters
755
-     * associated with all the senders that have a track attached to it.
786
+     * Munges the session description by setting the max bitrates on the video m-lines when VP9 K-SVC codec is in use.
756
      *
787
      *
757
-     * @param {boolean} enable - whether outgoing media needs to be enabled or disabled.
758
-     * @param {string} mediaType - media type, 'audio' or 'video', if neither is passed, all outgoing media will either
759
-     * be enabled or disabled.
760
-     * @returns {Promise} - A promise that is resolved when the change is succesful on all the senders, rejected
761
-     * otherwise.
788
+     * @param {RTCSessionDescription} description - The local/remote description that needs to be munged.
789
+     * @param {boolean} isLocalSdp - Whether the max bitrate (via b=AS line in SDP) is set on local SDP.
790
+     * @returns {RTCSessionDescription} - The munged local/remote description.
762
      */
791
      */
763
-    setMediaTransferActive(enable, mediaType) {
764
-        logger.info(`${this.pc} ${enable ? 'Resuming' : 'Suspending'} media transfer.`);
792
+    setMaxBitrates(description, isLocalSdp = false) {
793
+        const pcCodecSettings = this.pc.codecSettings;
794
+
795
+        if (!pcCodecSettings) {
796
+            return description;
797
+        }
798
+        const parsedSdp = transform.parse(description.sdp);
765
 
799
 
766
-        const senders = this.pc.peerconnection.getSenders()
767
-            .filter(s => Boolean(s.track) && (!mediaType || s.track.kind === mediaType));
768
-        const promises = [];
800
+        // Find all the m-lines associated with the local sources.
801
+        const direction = isLocalSdp ? MediaDirection.RECVONLY : MediaDirection.SENDONLY;
802
+        const mLines = parsedSdp.media.filter(m => m.type === MediaType.VIDEO && m.direction !== direction);
803
+        const currentCodec = pcCodecSettings.codecList[0];
804
+        const codecScalabilityModeSettings = this.codecSettings[currentCodec];
769
 
805
 
770
-        for (const sender of senders) {
771
-            if (sender.track.kind === MediaType.VIDEO) {
772
-                promises.push(this.pc._updateVideoSenderParameters(() => this._enableSenderEncodings(sender, enable)));
806
+        for (const mLine of mLines) {
807
+            const isDoingVp9KSvc = currentCodec === CodecMimeType.VP9
808
+                && !codecScalabilityModeSettings.scalabilityModeEnabled;
809
+            const localTrack = this.pc.getLocalVideoTracks()
810
+                .find(track => this.pc.localTrackTransceiverMids.get(track.rtcId) === mLine.mid.toString());
811
+
812
+            if (localTrack
813
+                && (isDoingVp9KSvc
814
+
815
+                    // Setting bitrates in the SDP for SVC codecs is no longer needed in the newer versions where
816
+                    // maxBitrates from the RTCRtpEncodingParameters directly affect the target bitrate for the encoder.
817
+                    || (this._isRunningInFullSvcMode(currentCodec) && !this.pc.usesCodecSelectionAPI()))) {
818
+                let maxBitrate;
819
+
820
+                if (localTrack.getVideoType() === VideoType.DESKTOP) {
821
+                    maxBitrate = codecScalabilityModeSettings.maxBitratesVideo.ssHigh;
822
+                } else {
823
+                    const { level } = VIDEO_QUALITY_LEVELS.find(lvl => lvl.height <= localTrack.getCaptureResolution());
824
+
825
+                    maxBitrate = codecScalabilityModeSettings.maxBitratesVideo[level];
826
+                }
827
+
828
+                const limit = Math.floor(maxBitrate / 1000);
829
+
830
+                // Use only the highest spatial layer bitrates for now as there is no API available yet for configuring
831
+                // the bitrates on the individual SVC layers.
832
+                mLine.bandwidth = [ {
833
+                    type: 'AS',
834
+                    limit
835
+                } ];
773
             } else {
836
             } else {
774
-                promises.push(this._enableSenderEncodings(sender, enable));
837
+                // Clear the bandwidth limit in SDP when VP9 is no longer the preferred codec.
838
+                // This is needed on react native clients as react-native-webrtc returns the
839
+                // SDP that the application passed instead of returning the SDP off the native side.
840
+                // This line automatically gets cleared on web on every renegotiation.
841
+                mLine.bandwidth = undefined;
775
             }
842
             }
776
         }
843
         }
777
 
844
 
778
-        return Promise.allSettled(promises)
779
-            .then(settledResult => {
780
-                const errors = settledResult
781
-                    .filter(result => result.status === 'rejected')
782
-                    .map(result => result.reason);
845
+        return new RTCSessionDescription({
846
+            type: description.type,
847
+            sdp: transform.write(parsedSdp)
848
+        });
849
+    }
850
+
851
+    /**
852
+     * Checks if the AV1 Dependency descriptors are negotiated on the bridge peerconnection and removes them from the
853
+     * description when codec selected is VP8 or VP9.
854
+     *
855
+     * @param {RTCSessionDescription} description that needs to be munged.
856
+     * @returns {RTCSessionDescription} the munged description.
857
+     */
858
+    updateAv1DdHeaders(description) {
859
+        const parsedSdp = transform.parse(description.sdp);
860
+        const mLines = parsedSdp.media.filter(m => m.type === MediaType.VIDEO);
861
+
862
+        if (!mLines.length || !browser.supportsDDExtHeaders()) {
863
+            return description;
864
+        }
865
+
866
+        mLines.forEach((mLine, idx) => {
867
+            const senderMids = Array.from(this.pc.localTrackTransceiverMids.values());
868
+            const isSender = senderMids.length
869
+                ? senderMids.find(mid => mLine.mid.toString() === mid.toString())
870
+                : idx === 0;
871
+            const payload = mLine.payloads.split(' ')[0];
872
+            let { codec } = mLine.rtp.find(rtp => rtp.payload === Number(payload));
873
+
874
+            codec = codec.toLowerCase();
783
 
875
 
784
-                if (errors.length) {
785
-                    return Promise.reject(new Error('Failed to change encodings on the RTCRtpSenders'
786
-                        + `${errors.join(' ')}`));
876
+            if (isSender && mLine.ext?.length) {
877
+                const headerIndex = mLine.ext.findIndex(ext => ext.uri === DD_HEADER_EXT_URI);
878
+                const shouldNegotiateHeaderExts = codec === CodecMimeType.AV1 || codec === CodecMimeType.H264;
879
+
880
+                if (!this.supportsDDHeaderExt && headerIndex >= 0) {
881
+                    this.supportsDDHeaderExt = true;
787
                 }
882
                 }
788
 
883
 
789
-                return Promise.resolve();
790
-            });
884
+                if (this.supportsDDHeaderExt && shouldNegotiateHeaderExts && headerIndex < 0) {
885
+                    mLine.ext.push({
886
+                        value: DD_HEADER_EXT_ID,
887
+                        uri: DD_HEADER_EXT_URI
888
+                    });
889
+                } else if (!shouldNegotiateHeaderExts && headerIndex >= 0) {
890
+                    mLine.ext.splice(headerIndex, 1);
891
+                }
892
+            }
893
+        });
894
+
895
+        return new RTCSessionDescription({
896
+            type: description.type,
897
+            sdp: transform.write(parsedSdp)
898
+        });
791
     }
899
     }
792
 }
900
 }

+ 40
- 77
modules/RTC/TPCUtils.spec.js Parādīt failu

35
 
35
 
36
         it('sort ssrcs associated with all FID ssrc-groups', () => {
36
         it('sort ssrcs associated with all FID ssrc-groups', () => {
37
             const pc = new MockPeerConnection();
37
             const pc = new MockPeerConnection();
38
-            const tpcUtils = new TPCUtils(pc);
38
+            const tpcUtils = new TPCUtils(pc, { });
39
             const source = new RTCSessionDescription({
39
             const source = new RTCSessionDescription({
40
                 type: 'offer',
40
                 type: 'offer',
41
                 sdp: getSourceSdp()
41
                 sdp: getSourceSdp()
99
 
99
 
100
         it('sort ssrcs in case the first ssrc in the SIM group is not present at the top', () => {
100
         it('sort ssrcs in case the first ssrc in the SIM group is not present at the top', () => {
101
             const pc = new MockPeerConnection();
101
             const pc = new MockPeerConnection();
102
-            const tpcUtils = new TPCUtils(pc);
102
+            const tpcUtils = new TPCUtils(pc, { });
103
             const source = new RTCSessionDescription({
103
             const source = new RTCSessionDescription({
104
                 type: 'offer',
104
                 type: 'offer',
105
                 sdp: getSourceSdp()
105
                 sdp: getSourceSdp()
163
 
163
 
164
         it('sort ssrcs in case there is a single FID group', () => {
164
         it('sort ssrcs in case there is a single FID group', () => {
165
             const pc = new MockPeerConnection();
165
             const pc = new MockPeerConnection();
166
-            const tpcUtils = new TPCUtils(pc);
166
+            const tpcUtils = new TPCUtils(pc, { });
167
             const source = new RTCSessionDescription({
167
             const source = new RTCSessionDescription({
168
                 type: 'offer',
168
                 type: 'offer',
169
                 sdp: getSourceSdp()
169
                 sdp: getSourceSdp()
223
 
223
 
224
             beforeEach(() => {
224
             beforeEach(() => {
225
                 pc = new MockPeerConnection('1', true, true /* simulcast */);
225
                 pc = new MockPeerConnection('1', true, true /* simulcast */);
226
-                pc.options = { videoQuality };
227
                 pc.videoTransferActive = true;
226
                 pc.videoTransferActive = true;
228
-                tpcUtils = new TPCUtils(pc);
227
+                tpcUtils = new TPCUtils(pc, { videoQuality });
229
             });
228
             });
230
 
229
 
231
             afterEach(() => {
230
             afterEach(() => {
311
 
310
 
312
             beforeEach(() => {
311
             beforeEach(() => {
313
                 pc = new MockPeerConnection('1', true, true /* simulcast */);
312
                 pc = new MockPeerConnection('1', true, true /* simulcast */);
314
-                pc.options = { videoQuality };
315
                 pc.videoTransferActive = true;
313
                 pc.videoTransferActive = true;
316
-                tpcUtils = new TPCUtils(pc);
314
+                tpcUtils = new TPCUtils(pc, { videoQuality });
317
             });
315
             });
318
 
316
 
319
             afterEach(() => {
317
             afterEach(() => {
442
             beforeEach(() => {
440
             beforeEach(() => {
443
                 pc = new MockPeerConnection('1', true, true /* simulcast */);
441
                 pc = new MockPeerConnection('1', true, true /* simulcast */);
444
                 pc._capScreenshareBitrate = true;
442
                 pc._capScreenshareBitrate = true;
445
-                pc.options = { videoQuality };
446
                 pc.videoTransferActive = true;
443
                 pc.videoTransferActive = true;
447
-                tpcUtils = new TPCUtils(pc);
444
+                tpcUtils = new TPCUtils(pc, { videoQuality });
448
             });
445
             });
449
 
446
 
450
             afterEach(() => {
447
             afterEach(() => {
489
             beforeEach(() => {
486
             beforeEach(() => {
490
                 pc = new MockPeerConnection('1', true, false /* simulcast */);
487
                 pc = new MockPeerConnection('1', true, false /* simulcast */);
491
                 pc._capScreenshareBitrate = true;
488
                 pc._capScreenshareBitrate = true;
492
-                pc.options = { videoQuality };
493
                 pc.videoTransferActive = true;
489
                 pc.videoTransferActive = true;
494
-                tpcUtils = new TPCUtils(pc);
490
+                tpcUtils = new TPCUtils(pc, { videoQuality });
495
             });
491
             });
496
 
492
 
497
             afterEach(() => {
493
             afterEach(() => {
531
             beforeEach(() => {
527
             beforeEach(() => {
532
                 pc = new MockPeerConnection('1', true, true /* simulcast */);
528
                 pc = new MockPeerConnection('1', true, true /* simulcast */);
533
                 pc._capScreenshareBitrate = false;
529
                 pc._capScreenshareBitrate = false;
534
-                pc.options = { videoQuality };
535
                 pc.videoTransferActive = true;
530
                 pc.videoTransferActive = true;
536
-                tpcUtils = new TPCUtils(pc);
531
+                tpcUtils = new TPCUtils(pc, { videoQuality });
537
             });
532
             });
538
 
533
 
539
             afterEach(() => {
534
             afterEach(() => {
577
 
572
 
578
             beforeEach(() => {
573
             beforeEach(() => {
579
                 pc = new MockPeerConnection('1', true, false /* simulcast */);
574
                 pc = new MockPeerConnection('1', true, false /* simulcast */);
580
-                pc.options = { videoQuality };
581
                 pc.videoTransferActive = true;
575
                 pc.videoTransferActive = true;
582
-                tpcUtils = new TPCUtils(pc);
576
+                tpcUtils = new TPCUtils(pc, { videoQuality });
583
             });
577
             });
584
 
578
 
585
             afterEach(() => {
579
             afterEach(() => {
660
 
654
 
661
             beforeEach(() => {
655
             beforeEach(() => {
662
                 pc = new MockPeerConnection('1', true, false /* simulcast */);
656
                 pc = new MockPeerConnection('1', true, false /* simulcast */);
663
-                pc.options = { videoQuality };
664
                 pc.videoTransferActive = true;
657
                 pc.videoTransferActive = true;
665
-                tpcUtils = new TPCUtils(pc);
658
+                tpcUtils = new TPCUtils(pc, { videoQuality });
666
             });
659
             });
667
 
660
 
668
             afterEach(() => {
661
             afterEach(() => {
698
 
691
 
699
             beforeEach(() => {
692
             beforeEach(() => {
700
                 pc = new MockPeerConnection('1', true, true /* simulcast */);
693
                 pc = new MockPeerConnection('1', true, true /* simulcast */);
701
-                pc.options = { videoQuality };
702
                 pc.videoTransferActive = true;
694
                 pc.videoTransferActive = true;
703
-                tpcUtils = new TPCUtils(pc);
695
+                tpcUtils = new TPCUtils(pc, { videoQuality });
704
             });
696
             });
705
 
697
 
706
             afterEach(() => {
698
             afterEach(() => {
787
             beforeEach(() => {
779
             beforeEach(() => {
788
                 pc = new MockPeerConnection('1', true, true /* simulcast */);
780
                 pc = new MockPeerConnection('1', true, true /* simulcast */);
789
                 pc._capScreenshareBitrate = true;
781
                 pc._capScreenshareBitrate = true;
790
-                pc.options = { videoQuality };
791
                 pc.videoTransferActive = true;
782
                 pc.videoTransferActive = true;
792
-                tpcUtils = new TPCUtils(pc);
783
+                tpcUtils = new TPCUtils(pc, { videoQuality });
793
             });
784
             });
794
 
785
 
795
             afterEach(() => {
786
             afterEach(() => {
834
             beforeEach(() => {
825
             beforeEach(() => {
835
                 pc = new MockPeerConnection('1', true, true /* simulcast */);
826
                 pc = new MockPeerConnection('1', true, true /* simulcast */);
836
                 pc._capScreenshareBitrate = false;
827
                 pc._capScreenshareBitrate = false;
837
-                pc.options = { videoQuality };
838
                 pc.videoTransferActive = true;
828
                 pc.videoTransferActive = true;
839
-                tpcUtils = new TPCUtils(pc);
829
+                tpcUtils = new TPCUtils(pc, { videoQuality });
840
             });
830
             });
841
 
831
 
842
             afterEach(() => {
832
             afterEach(() => {
880
 
870
 
881
             beforeEach(() => {
871
             beforeEach(() => {
882
                 pc = new MockPeerConnection('1', true, true /* simulcast */);
872
                 pc = new MockPeerConnection('1', true, true /* simulcast */);
883
-                pc.options = { videoQuality };
884
                 pc.videoTransferActive = true;
873
                 pc.videoTransferActive = true;
885
-                tpcUtils = new TPCUtils(pc);
874
+                tpcUtils = new TPCUtils(pc, { videoQuality });
886
             });
875
             });
887
 
876
 
888
             afterEach(() => {
877
             afterEach(() => {
989
 
978
 
990
             beforeEach(() => {
979
             beforeEach(() => {
991
                 pc = new MockPeerConnection('1', true, true /* simulcast */);
980
                 pc = new MockPeerConnection('1', true, true /* simulcast */);
992
-                pc.options = { videoQuality };
993
                 pc.videoTransferActive = true;
981
                 pc.videoTransferActive = true;
994
-                tpcUtils = new TPCUtils(pc);
982
+                tpcUtils = new TPCUtils(pc, { videoQuality });
995
             });
983
             });
996
 
984
 
997
             afterEach(() => {
985
             afterEach(() => {
1098
 
1086
 
1099
             beforeEach(() => {
1087
             beforeEach(() => {
1100
                 pc = new MockPeerConnection('1', true, false /* simulcast */);
1088
                 pc = new MockPeerConnection('1', true, false /* simulcast */);
1101
-                pc.options = { videoQuality };
1102
                 pc.videoTransferActive = true;
1089
                 pc.videoTransferActive = true;
1103
-                tpcUtils = new TPCUtils(pc);
1090
+                tpcUtils = new TPCUtils(pc, { videoQuality });
1104
             });
1091
             });
1105
 
1092
 
1106
             afterEach(() => {
1093
             afterEach(() => {
1166
 
1153
 
1167
             beforeEach(() => {
1154
             beforeEach(() => {
1168
                 pc = new MockPeerConnection('1', true, false /* simulcast */);
1155
                 pc = new MockPeerConnection('1', true, false /* simulcast */);
1169
-                pc.options = { videoQuality };
1170
                 pc.videoTransferActive = true;
1156
                 pc.videoTransferActive = true;
1171
-                tpcUtils = new TPCUtils(pc);
1157
+                tpcUtils = new TPCUtils(pc, { videoQuality });
1172
             });
1158
             });
1173
 
1159
 
1174
             afterEach(() => {
1160
             afterEach(() => {
1204
 
1190
 
1205
             beforeEach(() => {
1191
             beforeEach(() => {
1206
                 pc = new MockPeerConnection('1', true, true /* simulcast */);
1192
                 pc = new MockPeerConnection('1', true, true /* simulcast */);
1207
-                pc.options = { videoQuality };
1208
                 pc.videoTransferActive = true;
1193
                 pc.videoTransferActive = true;
1209
-                tpcUtils = new TPCUtils(pc);
1194
+                tpcUtils = new TPCUtils(pc, { videoQuality });
1210
             });
1195
             });
1211
 
1196
 
1212
             afterEach(() => {
1197
             afterEach(() => {
1299
             beforeEach(() => {
1284
             beforeEach(() => {
1300
                 pc = new MockPeerConnection('1', true, true /* simulcast */);
1285
                 pc = new MockPeerConnection('1', true, true /* simulcast */);
1301
                 pc._capScreenshareBitrate = true;
1286
                 pc._capScreenshareBitrate = true;
1302
-                pc.options = { videoQuality };
1303
                 pc.videoTransferActive = true;
1287
                 pc.videoTransferActive = true;
1304
-                tpcUtils = new TPCUtils(pc);
1288
+                tpcUtils = new TPCUtils(pc, { videoQuality });
1305
             });
1289
             });
1306
 
1290
 
1307
             afterEach(() => {
1291
             afterEach(() => {
1348
             beforeEach(() => {
1332
             beforeEach(() => {
1349
                 pc = new MockPeerConnection('1', true, true /* simulcast */);
1333
                 pc = new MockPeerConnection('1', true, true /* simulcast */);
1350
                 pc._capScreenshareBitrate = false;
1334
                 pc._capScreenshareBitrate = false;
1351
-                pc.options = { videoQuality };
1352
                 pc.videoTransferActive = true;
1335
                 pc.videoTransferActive = true;
1353
-                tpcUtils = new TPCUtils(pc);
1336
+                tpcUtils = new TPCUtils(pc, { videoQuality });
1354
             });
1337
             });
1355
 
1338
 
1356
             afterEach(() => {
1339
             afterEach(() => {
1396
 
1379
 
1397
             beforeEach(() => {
1380
             beforeEach(() => {
1398
                 pc = new MockPeerConnection('1', true, false /* simulcast */);
1381
                 pc = new MockPeerConnection('1', true, false /* simulcast */);
1399
-                pc.options = { videoQuality };
1400
                 pc.videoTransferActive = true;
1382
                 pc.videoTransferActive = true;
1401
-                tpcUtils = new TPCUtils(pc);
1383
+                tpcUtils = new TPCUtils(pc, { videoQuality });
1402
             });
1384
             });
1403
 
1385
 
1404
             afterEach(() => {
1386
             afterEach(() => {
1464
 
1446
 
1465
             beforeEach(() => {
1447
             beforeEach(() => {
1466
                 pc = new MockPeerConnection('1', true, false /* simulcast */);
1448
                 pc = new MockPeerConnection('1', true, false /* simulcast */);
1467
-                pc.options = { videoQuality };
1468
                 pc.videoTransferActive = true;
1449
                 pc.videoTransferActive = true;
1469
-                tpcUtils = new TPCUtils(pc);
1450
+                tpcUtils = new TPCUtils(pc, { videoQuality });
1470
             });
1451
             });
1471
 
1452
 
1472
             afterEach(() => {
1453
             afterEach(() => {
1502
 
1483
 
1503
             beforeEach(() => {
1484
             beforeEach(() => {
1504
                 pc = new MockPeerConnection('1', true, true /* simulcast */);
1485
                 pc = new MockPeerConnection('1', true, true /* simulcast */);
1505
-                pc.options = { videoQuality };
1506
                 pc.videoTransferActive = true;
1486
                 pc.videoTransferActive = true;
1507
-                tpcUtils = new TPCUtils(pc);
1487
+                tpcUtils = new TPCUtils(pc, { videoQuality });
1508
             });
1488
             });
1509
 
1489
 
1510
             afterEach(() => {
1490
             afterEach(() => {
1614
             beforeEach(() => {
1594
             beforeEach(() => {
1615
                 pc = new MockPeerConnection('1', true, true /* simulcast */);
1595
                 pc = new MockPeerConnection('1', true, true /* simulcast */);
1616
                 pc._capScreenshareBitrate = true;
1596
                 pc._capScreenshareBitrate = true;
1617
-                pc.options = { videoQuality };
1618
                 pc.videoTransferActive = true;
1597
                 pc.videoTransferActive = true;
1619
-                tpcUtils = new TPCUtils(pc);
1598
+                tpcUtils = new TPCUtils(pc, { videoQuality });
1620
             });
1599
             });
1621
 
1600
 
1622
             afterEach(() => {
1601
             afterEach(() => {
1661
             beforeEach(() => {
1640
             beforeEach(() => {
1662
                 pc = new MockPeerConnection('1', true, true /* simulcast */);
1641
                 pc = new MockPeerConnection('1', true, true /* simulcast */);
1663
                 pc._capScreenshareBitrate = false;
1642
                 pc._capScreenshareBitrate = false;
1664
-                pc.options = { videoQuality };
1665
                 pc.videoTransferActive = true;
1643
                 pc.videoTransferActive = true;
1666
-                tpcUtils = new TPCUtils(pc);
1644
+                tpcUtils = new TPCUtils(pc, { videoQuality });
1667
             });
1645
             });
1668
 
1646
 
1669
             afterEach(() => {
1647
             afterEach(() => {
1707
 
1685
 
1708
             beforeEach(() => {
1686
             beforeEach(() => {
1709
                 pc = new MockPeerConnection('1', true, false /* simulcast */);
1687
                 pc = new MockPeerConnection('1', true, false /* simulcast */);
1710
-                pc.options = { videoQuality };
1711
                 pc.videoTransferActive = true;
1688
                 pc.videoTransferActive = true;
1712
-                tpcUtils = new TPCUtils(pc);
1689
+                tpcUtils = new TPCUtils(pc, { videoQuality });
1713
             });
1690
             });
1714
 
1691
 
1715
             afterEach(() => {
1692
             afterEach(() => {
1775
 
1752
 
1776
             beforeEach(() => {
1753
             beforeEach(() => {
1777
                 pc = new MockPeerConnection('1', true, false /* simulcast */);
1754
                 pc = new MockPeerConnection('1', true, false /* simulcast */);
1778
-                pc.options = { videoQuality };
1779
                 pc.videoTransferActive = true;
1755
                 pc.videoTransferActive = true;
1780
-                tpcUtils = new TPCUtils(pc);
1756
+                tpcUtils = new TPCUtils(pc, { videoQuality });
1781
             });
1757
             });
1782
 
1758
 
1783
             afterEach(() => {
1759
             afterEach(() => {
1843
 
1819
 
1844
             beforeEach(() => {
1820
             beforeEach(() => {
1845
                 pc = new MockPeerConnection('1', true, false /* simulcast */);
1821
                 pc = new MockPeerConnection('1', true, false /* simulcast */);
1846
-                pc.options = { videoQuality };
1847
                 pc._capScreenshareBitrate = false;
1822
                 pc._capScreenshareBitrate = false;
1848
                 pc.videoTransferActive = true;
1823
                 pc.videoTransferActive = true;
1849
-                tpcUtils = new TPCUtils(pc);
1824
+                tpcUtils = new TPCUtils(pc, { videoQuality });
1850
             });
1825
             });
1851
 
1826
 
1852
             afterEach(() => {
1827
             afterEach(() => {
1908
 
1883
 
1909
             beforeEach(() => {
1884
             beforeEach(() => {
1910
                 pc = new MockPeerConnection('1', true, true /* simulcast */);
1885
                 pc = new MockPeerConnection('1', true, true /* simulcast */);
1911
-                pc.options = { videoQuality };
1912
                 pc.videoTransferActive = true;
1886
                 pc.videoTransferActive = true;
1913
-                tpcUtils = new TPCUtils(pc);
1887
+                tpcUtils = new TPCUtils(pc, { videoQuality });
1914
             });
1888
             });
1915
 
1889
 
1916
             afterEach(() => {
1890
             afterEach(() => {
2034
 
2008
 
2035
             beforeEach(() => {
2009
             beforeEach(() => {
2036
                 pc = new MockPeerConnection('1', true, true /* simulcast */);
2010
                 pc = new MockPeerConnection('1', true, true /* simulcast */);
2037
-                pc.options = { videoQuality };
2038
                 pc.videoTransferActive = true;
2011
                 pc.videoTransferActive = true;
2039
-                tpcUtils = new TPCUtils(pc);
2012
+                tpcUtils = new TPCUtils(pc, { videoQuality });
2040
             });
2013
             });
2041
 
2014
 
2042
             afterEach(() => {
2015
             afterEach(() => {
2154
 
2127
 
2155
             beforeEach(() => {
2128
             beforeEach(() => {
2156
                 pc = new MockPeerConnection('1', true, true /* simulcast */);
2129
                 pc = new MockPeerConnection('1', true, true /* simulcast */);
2157
-                pc.options = { videoQuality };
2158
                 pc.videoTransferActive = true;
2130
                 pc.videoTransferActive = true;
2159
-                tpcUtils = new TPCUtils(pc);
2131
+                tpcUtils = new TPCUtils(pc, { videoQuality });
2160
             });
2132
             });
2161
 
2133
 
2162
             afterEach(() => {
2134
             afterEach(() => {
2266
 
2238
 
2267
             beforeEach(() => {
2239
             beforeEach(() => {
2268
                 pc = new MockPeerConnection('1', true, true /* simulcast */);
2240
                 pc = new MockPeerConnection('1', true, true /* simulcast */);
2269
-                pc.options = { videoQuality };
2270
                 pc.videoTransferActive = true;
2241
                 pc.videoTransferActive = true;
2271
-                tpcUtils = new TPCUtils(pc);
2242
+                tpcUtils = new TPCUtils(pc, { videoQuality });
2272
             });
2243
             });
2273
 
2244
 
2274
             afterEach(() => {
2245
             afterEach(() => {
2378
 
2349
 
2379
             beforeEach(() => {
2350
             beforeEach(() => {
2380
                 pc = new MockPeerConnection('1', true, true /* simulcast */);
2351
                 pc = new MockPeerConnection('1', true, true /* simulcast */);
2381
-                pc.options = { videoQuality };
2382
                 pc._capScreenshareBitrate = true;
2352
                 pc._capScreenshareBitrate = true;
2383
                 pc.videoTransferActive = true;
2353
                 pc.videoTransferActive = true;
2384
-                tpcUtils = new TPCUtils(pc);
2354
+                tpcUtils = new TPCUtils(pc, { videoQuality });
2385
             });
2355
             });
2386
 
2356
 
2387
             afterEach(() => {
2357
             afterEach(() => {
2447
 
2417
 
2448
             beforeEach(() => {
2418
             beforeEach(() => {
2449
                 pc = new MockPeerConnection('1', true, true /* simulcast */);
2419
                 pc = new MockPeerConnection('1', true, true /* simulcast */);
2450
-                pc.options = { videoQuality };
2451
                 pc._capScreenshareBitrate = false;
2420
                 pc._capScreenshareBitrate = false;
2452
                 pc.videoTransferActive = true;
2421
                 pc.videoTransferActive = true;
2453
-                tpcUtils = new TPCUtils(pc);
2422
+                tpcUtils = new TPCUtils(pc, { videoQuality });
2454
             });
2423
             });
2455
 
2424
 
2456
             afterEach(() => {
2425
             afterEach(() => {
2516
 
2485
 
2517
             beforeEach(() => {
2486
             beforeEach(() => {
2518
                 pc = new MockPeerConnection('1', true, false /* simulcast */);
2487
                 pc = new MockPeerConnection('1', true, false /* simulcast */);
2519
-                pc.options = { videoQuality };
2520
                 pc.videoTransferActive = true;
2488
                 pc.videoTransferActive = true;
2521
-                tpcUtils = new TPCUtils(pc);
2489
+                tpcUtils = new TPCUtils(pc, { videoQuality });
2522
             });
2490
             });
2523
 
2491
 
2524
             afterEach(() => {
2492
             afterEach(() => {
2602
 
2570
 
2603
             beforeEach(() => {
2571
             beforeEach(() => {
2604
                 pc = new MockPeerConnection('1', true, true /* simulcast */);
2572
                 pc = new MockPeerConnection('1', true, true /* simulcast */);
2605
-                pc.options = { videoQuality };
2606
                 pc.videoTransferActive = true;
2573
                 pc.videoTransferActive = true;
2607
-                tpcUtils = new TPCUtils(pc);
2574
+                tpcUtils = new TPCUtils(pc, { videoQuality });
2608
             });
2575
             });
2609
 
2576
 
2610
             afterEach(() => {
2577
             afterEach(() => {
2689
 
2656
 
2690
             beforeEach(() => {
2657
             beforeEach(() => {
2691
                 pc = new MockPeerConnection('1', true, true /* simulcast */);
2658
                 pc = new MockPeerConnection('1', true, true /* simulcast */);
2692
-                pc.options = { videoQuality };
2693
                 pc.videoTransferActive = true;
2659
                 pc.videoTransferActive = true;
2694
-                tpcUtils = new TPCUtils(pc);
2660
+                tpcUtils = new TPCUtils(pc, { videoQuality });
2695
             });
2661
             });
2696
 
2662
 
2697
             afterEach(() => {
2663
             afterEach(() => {
2776
             };
2742
             };
2777
 
2743
 
2778
             pc = new MockPeerConnection('1', true, true /* simulcast */);
2744
             pc = new MockPeerConnection('1', true, true /* simulcast */);
2779
-            pc.options = { videoQuality };
2780
             pc._capScreenshareBitrate = true;
2745
             pc._capScreenshareBitrate = true;
2781
             pc.videoTransferActive = true;
2746
             pc.videoTransferActive = true;
2782
-            const utils = new TPCUtils(pc);
2747
+            const utils = new TPCUtils(pc, { videoQuality });
2783
 
2748
 
2784
             it('and requested desktop resolution is 2160', () => {
2749
             it('and requested desktop resolution is 2160', () => {
2785
                 height = 2160;
2750
                 height = 2160;
2894
             const videoQuality = {};
2859
             const videoQuality = {};
2895
 
2860
 
2896
             pc = new MockPeerConnection('1', true, true /* simulcast */);
2861
             pc = new MockPeerConnection('1', true, true /* simulcast */);
2897
-            pc.options = { videoQuality };
2898
             pc._capScreenshareBitrate = true;
2862
             pc._capScreenshareBitrate = true;
2899
             pc.videoTransferActive = true;
2863
             pc.videoTransferActive = true;
2900
-            const utils = new TPCUtils(pc);
2864
+            const utils = new TPCUtils(pc, { videoQuality });
2901
 
2865
 
2902
             it('and requested desktop resolution is 2160', () => {
2866
             it('and requested desktop resolution is 2160', () => {
2903
                 height = 2160;
2867
                 height = 2160;
3003
 
2967
 
3004
             beforeEach(() => {
2968
             beforeEach(() => {
3005
                 pc = new MockPeerConnection('1', true, true /* simulcast */);
2969
                 pc = new MockPeerConnection('1', true, true /* simulcast */);
3006
-                pc.options = {};
3007
                 pc.videoTransferActive = false;
2970
                 pc.videoTransferActive = false;
3008
-                tpcUtils = new TPCUtils(pc);
2971
+                tpcUtils = new TPCUtils(pc, { videoQuality: {} });
3009
             });
2972
             });
3010
 
2973
 
3011
             afterEach(() => {
2974
             afterEach(() => {

+ 249
- 376
modules/RTC/TraceablePeerConnection.js Parādīt failu

31
 const logger = getLogger(__filename);
31
 const logger = getLogger(__filename);
32
 const DEGRADATION_PREFERENCE_CAMERA = 'maintain-framerate';
32
 const DEGRADATION_PREFERENCE_CAMERA = 'maintain-framerate';
33
 const DEGRADATION_PREFERENCE_DESKTOP = 'maintain-resolution';
33
 const DEGRADATION_PREFERENCE_DESKTOP = 'maintain-resolution';
34
-const DD_HEADER_EXT_URI
35
-    = 'https://aomediacodec.github.io/av1-rtp-spec/#dependency-descriptor-rtp-header-extension';
36
-const DD_HEADER_EXT_ID = 11;
37
 
34
 
38
 /* eslint-disable max-params */
35
 /* eslint-disable max-params */
39
 
36
 
245
 
242
 
246
     this.peerconnection = new RTCPeerConnection(pcConfig, safeConstraints);
243
     this.peerconnection = new RTCPeerConnection(pcConfig, safeConstraints);
247
 
244
 
248
-    this.tpcUtils = new TPCUtils(this);
245
+    this.tpcUtils = new TPCUtils(this, {
246
+        audioQuality: options.audioQuality,
247
+        isP2P: this.isP2P,
248
+        videoQuality: options.videoQuality
249
+    });
249
     this.updateLog = [];
250
     this.updateLog = [];
250
     this.stats = {};
251
     this.stats = {};
251
     this.statsinterval = null;
252
     this.statsinterval = null;
311
      */
312
      */
312
     this._senderMaxHeights = new Map();
313
     this._senderMaxHeights = new Map();
313
 
314
 
314
-    /**
315
-     * Flag indicating bridge support for AV1 codec. On the bridge connection, it is supported only when support for
316
-     * Dependency Descriptor header extensions is offered by Jicofo. H.264 simulcast is also possible when these
317
-     * header extensions are negotiated.
318
-     */
319
-    this._supportsDDHeaderExt = false;
320
-
321
     /**
315
     /**
322
      * Holds the RTCRtpTransceiver mids that the local tracks are attached to, mapped per their
316
      * Holds the RTCRtpTransceiver mids that the local tracks are attached to, mapped per their
323
      * {@link JitsiLocalTrack.rtcId}.
317
      * {@link JitsiLocalTrack.rtcId}.
324
      * @type {Map<string, string>}
318
      * @type {Map<string, string>}
325
      */
319
      */
326
-    this._localTrackTransceiverMids = new Map();
320
+    this.localTrackTransceiverMids = new Map();
327
 
321
 
328
     /**
322
     /**
329
      * Holds the SSRC map for the local tracks mapped by their source names.
323
      * Holds the SSRC map for the local tracks mapped by their source names.
505
  * Obtains the media direction for given {@link MediaType} that needs to be set on a p2p peerconnection's remote SDP
499
  * Obtains the media direction for given {@link MediaType} that needs to be set on a p2p peerconnection's remote SDP
506
  * after a source-add or source-remove action. The method takes into account whether or not there are any
500
  * after a source-add or source-remove action. The method takes into account whether or not there are any
507
  * local tracks for the given media type.
501
  * local tracks for the given media type.
508
- * @param {MediaType} mediaType
502
+ * @param {MediaType} mediaType - The media type for which the direction is to be calculated.
509
  * @param {boolean} isAddOperation whether the direction is to be calculated after a source-add action.
503
  * @param {boolean} isAddOperation whether the direction is to be calculated after a source-add action.
510
  * @return {string} one of the SDP direction constants ('sendrecv, 'recvonly' etc.) which should be used when setting
504
  * @return {string} one of the SDP direction constants ('sendrecv, 'recvonly' etc.) which should be used when setting
511
  * local description on the peerconnection.
505
  * local description on the peerconnection.
512
  * @private
506
  * @private
513
  */
507
  */
514
 TraceablePeerConnection.prototype.getDesiredMediaDirection = function(mediaType, isAddOperation = false) {
508
 TraceablePeerConnection.prototype.getDesiredMediaDirection = function(mediaType, isAddOperation = false) {
515
-    const hasLocalSource = this.hasAnyTracksOfType(mediaType);
516
-
517
-    if (isAddOperation) {
518
-        return hasLocalSource ? MediaDirection.SENDRECV : MediaDirection.SENDONLY;
519
-    }
520
-
521
-    return hasLocalSource ? MediaDirection.RECVONLY : MediaDirection.INACTIVE;
509
+    return this.tpcUtils.getDesiredMediaDirection(mediaType, isAddOperation);
522
 };
510
 };
523
 
511
 
524
 /**
512
 /**
675
  * @returns {boolean}
663
  * @returns {boolean}
676
  */
664
  */
677
 TraceablePeerConnection.prototype.doesTrueSimulcast = function(localTrack) {
665
 TraceablePeerConnection.prototype.doesTrueSimulcast = function(localTrack) {
678
-    const currentCodec = this.getConfiguredVideoCodec(localTrack);
666
+    const currentCodec = this.tpcUtils.getConfiguredVideoCodec(localTrack);
679
 
667
 
680
     return this.isSpatialScalabilityOn() && this.tpcUtils.isRunningInSimulcastMode(currentCodec);
668
     return this.isSpatialScalabilityOn() && this.tpcUtils.isRunningInSimulcastMode(currentCodec);
681
 };
669
 };
723
     return this.getLocalTracks(MediaType.VIDEO);
711
     return this.getLocalTracks(MediaType.VIDEO);
724
 };
712
 };
725
 
713
 
726
-/**
727
- * Checks whether or not this {@link TraceablePeerConnection} instance contains any local tracks for given
728
- * <tt>mediaType</tt>.
729
- *
730
- * @param {MediaType} mediaType - The media type.
731
- * @return {boolean}
732
- */
733
-TraceablePeerConnection.prototype.hasAnyTracksOfType = function(mediaType) {
734
-    if (!mediaType) {
735
-        throw new Error('"mediaType" is required');
736
-    }
737
-
738
-    return this.getLocalTracks(mediaType).length > 0;
739
-};
740
-
741
 /**
714
 /**
742
  * Obtains all remote tracks currently known to this PeerConnection instance.
715
  * Obtains all remote tracks currently known to this PeerConnection instance.
743
  *
716
  *
822
  * @returns {Object}
795
  * @returns {Object}
823
  */
796
  */
824
 TraceablePeerConnection.prototype.getTargetVideoBitrates = function(localTrack) {
797
 TraceablePeerConnection.prototype.getTargetVideoBitrates = function(localTrack) {
825
-    const currentCodec = this.getConfiguredVideoCodec(localTrack);
798
+    const currentCodec = this.tpcUtils.getConfiguredVideoCodec(localTrack);
826
 
799
 
827
     return this.tpcUtils.codecSettings[currentCodec].maxBitratesVideo;
800
     return this.tpcUtils.codecSettings[currentCodec].maxBitratesVideo;
828
 };
801
 };
1352
     return Boolean(tracks.find(track => track.videoType === VideoType.DESKTOP));
1325
     return Boolean(tracks.find(track => track.videoType === VideoType.DESKTOP));
1353
 };
1326
 };
1354
 
1327
 
1355
-/**
1356
- * Munges the order of the codecs in the SDP passed based on the preference
1357
- * set through config.js settings. All instances of the specified codec are
1358
- * moved up to the top of the list when it is preferred. The specified codec
1359
- * is deleted from the list if the configuration specifies that the codec be
1360
- * disabled.
1361
- * @param {RTCSessionDescription} description that needs to be munged.
1362
- * @returns {RTCSessionDescription} the munged description.
1363
- */
1364
-TraceablePeerConnection.prototype._mungeCodecOrder = function(description) {
1365
-    if (!this.codecSettings) {
1366
-        return description;
1367
-    }
1368
-
1369
-    const parsedSdp = transform.parse(description.sdp);
1370
-    const mLines = parsedSdp.media.filter(m => m.type === this.codecSettings.mediaType);
1371
-
1372
-    if (!mLines.length) {
1373
-        return description;
1374
-    }
1375
-
1376
-    for (const mLine of mLines) {
1377
-        const currentCodecs = this.getConfiguredVideoCodecs(description);
1378
-
1379
-        for (const codec of currentCodecs) {
1380
-            if (this.isP2P) {
1381
-                // 1. Strip the high profile H264 codecs on all clients. macOS started offering encoder for H.264 level
1382
-                //     5.2 but a decoder only for level 3.1. Therfore, strip all main and high level codecs for H.264.
1383
-                // 2. There are multiple VP9 payload types generated by the browser, more payload types are added if the
1384
-                //     endpoint doesn't have a local video source. Therefore, strip all the high profile codec variants
1385
-                //     for VP9 so that only one payload type for VP9 is negotiated between the peers.
1386
-                if (codec === CodecMimeType.H264 || codec === CodecMimeType.VP9) {
1387
-                    SDPUtil.stripCodec(mLine, codec, true /* high profile */);
1388
-                }
1389
-
1390
-                // Do not negotiate ULPFEC and RED either.
1391
-                if (codec === CodecMimeType.ULPFEC || codec === CodecMimeType.RED) {
1392
-                    SDPUtil.stripCodec(mLine, codec, false);
1393
-                }
1394
-            }
1395
-        }
1396
-
1397
-        // Reorder the codecs based on the preferred settings.
1398
-        if (!this.usesCodecSelectionAPI()) {
1399
-            for (const codec of this.codecSettings.codecList.slice().reverse()) {
1400
-                SDPUtil.preferCodec(mLine, codec, this.isP2P);
1401
-            }
1402
-        }
1403
-    }
1404
-
1405
-    return new RTCSessionDescription({
1406
-        type: description.type,
1407
-        sdp: transform.write(parsedSdp)
1408
-    });
1409
-};
1410
-
1411
-/**
1412
- * Checks if the AV1 Dependency descriptors are negotiated on the bridge peerconnection and disables them when the
1413
- * codec selected is VP8 or VP9.
1414
- *
1415
- * @param {RTCSessionDescription} description that needs to be munged.
1416
- * @returns {RTCSessionDescription} the munged description.
1417
- */
1418
-TraceablePeerConnection.prototype._updateAv1DdHeaders = function(description) {
1419
-    const parsedSdp = transform.parse(description.sdp);
1420
-    const mLines = parsedSdp.media.filter(m => m.type === MediaType.VIDEO);
1421
-
1422
-    if (!mLines.length || !browser.supportsDDExtHeaders()) {
1423
-        return description;
1424
-    }
1425
-
1426
-    mLines.forEach((mLine, idx) => {
1427
-        const senderMids = Array.from(this._localTrackTransceiverMids.values());
1428
-        const isSender = senderMids.length
1429
-            ? senderMids.find(mid => mLine.mid.toString() === mid.toString())
1430
-            : idx === 0;
1431
-        const payload = mLine.payloads.split(' ')[0];
1432
-        let { codec } = mLine.rtp.find(rtp => rtp.payload === Number(payload));
1433
-
1434
-        codec = codec.toLowerCase();
1435
-
1436
-        if (isSender && mLine.ext?.length) {
1437
-            const headerIndex = mLine.ext.findIndex(ext => ext.uri === DD_HEADER_EXT_URI);
1438
-            const shouldNegotiateHeaderExts = codec === CodecMimeType.AV1 || codec === CodecMimeType.H264;
1439
-
1440
-            if (!this._supportsDDHeaderExt && headerIndex >= 0) {
1441
-                this._supportsDDHeaderExt = true;
1442
-            }
1443
-
1444
-            if (this._supportsDDHeaderExt && shouldNegotiateHeaderExts && headerIndex < 0) {
1445
-                mLine.ext.push({
1446
-                    value: DD_HEADER_EXT_ID,
1447
-                    uri: DD_HEADER_EXT_URI
1448
-                });
1449
-            } else if (!shouldNegotiateHeaderExts && headerIndex >= 0) {
1450
-                mLine.ext.splice(headerIndex, 1);
1451
-            }
1452
-        }
1453
-    });
1454
-
1455
-    return new RTCSessionDescription({
1456
-        type: description.type,
1457
-        sdp: transform.write(parsedSdp)
1458
-    });
1459
-};
1460
-
1461
 /**
1328
 /**
1462
  * Add {@link JitsiLocalTrack} to this TPC.
1329
  * Add {@link JitsiLocalTrack} to this TPC.
1463
  * @param {JitsiLocalTrack} track
1330
  * @param {JitsiLocalTrack} track
1466
  */
1333
  */
1467
 TraceablePeerConnection.prototype.addTrack = function(track, isInitiator = false) {
1334
 TraceablePeerConnection.prototype.addTrack = function(track, isInitiator = false) {
1468
     const rtcId = track.rtcId;
1335
     const rtcId = track.rtcId;
1469
-    let transceiver;
1470
 
1336
 
1471
-    logger.info(`${this} adding ${track}`);
1472
     if (this.localTracks.has(rtcId)) {
1337
     if (this.localTracks.has(rtcId)) {
1473
-
1474
         return Promise.reject(new Error(`${track} is already in ${this}`));
1338
         return Promise.reject(new Error(`${track} is already in ${this}`));
1475
     }
1339
     }
1476
 
1340
 
1477
-    this.localTracks.set(rtcId, track);
1341
+    logger.info(`${this} adding ${track}`);
1478
     const webrtcStream = track.getOriginalStream();
1342
     const webrtcStream = track.getOriginalStream();
1343
+    const mediaStreamTrack = track.getTrack();
1344
+    let transceiver;
1345
+
1346
+    if (isInitiator) {
1347
+        const streams = [];
1479
 
1348
 
1480
-    try {
1481
-        transceiver = this.tpcUtils.addTrack(track, isInitiator);
1482
-    } catch (error) {
1483
-        logger.error(`${this} Adding track=${track} failed: ${error?.message}`);
1349
+        webrtcStream && streams.push(webrtcStream);
1484
 
1350
 
1485
-        return Promise.reject(error);
1351
+        // Use pc.addTransceiver() for the initiator case when local tracks are getting added
1352
+        // to the peerconnection before a session-initiate is sent over to the peer.
1353
+        const transceiverInit = {
1354
+            direction: MediaDirection.SENDRECV,
1355
+            streams,
1356
+            sendEncodings: []
1357
+        };
1358
+
1359
+        if (!browser.isFirefox()) {
1360
+            transceiverInit.sendEncodings = this.tpcUtils.getStreamEncodings(track);
1361
+        }
1362
+
1363
+        try {
1364
+            transceiver = this.peerconnection.addTransceiver(mediaStreamTrack, transceiverInit);
1365
+        } catch (error) {
1366
+            return Promise.reject(new Error(`${this} failed to create transceiver for ${track} - ${error}`));
1367
+        }
1368
+    } else {
1369
+        // Use pc.addTrack() for responder case so that we can re-use the m-lines that were created
1370
+        // when setRemoteDescription was called. pc.addTrack() automatically  attaches to any existing
1371
+        // unused "recv-only" transceiver.
1372
+        const sender = this.peerconnection.addTrack(mediaStreamTrack);
1373
+
1374
+        // Find the corresponding transceiver that the track was attached to.
1375
+        transceiver = this.peerconnection.getTransceivers().find(t => t.sender === sender);
1486
     }
1376
     }
1487
 
1377
 
1488
     if (transceiver?.mid) {
1378
     if (transceiver?.mid) {
1489
-        this._localTrackTransceiverMids.set(track.rtcId, transceiver.mid.toString());
1379
+        this.localTrackTransceiverMids.set(track.rtcId, transceiver.mid.toString());
1490
     }
1380
     }
1491
 
1381
 
1492
     if (track) {
1382
     if (track) {
1383
+        this.localTracks.set(rtcId, track);
1493
         if (track.isAudioTrack()) {
1384
         if (track.isAudioTrack()) {
1494
             this._hasHadAudioTrack = true;
1385
             this._hasHadAudioTrack = true;
1495
         } else {
1386
         } else {
1501
 
1392
 
1502
     // On Firefox, the encodings have to be configured on the sender only after the transceiver is created.
1393
     // On Firefox, the encodings have to be configured on the sender only after the transceiver is created.
1503
     if (browser.isFirefox()) {
1394
     if (browser.isFirefox()) {
1504
-        promiseChain = promiseChain.then(() => webrtcStream && this.tpcUtils.setEncodings(track));
1395
+        promiseChain = promiseChain.then(() => webrtcStream && this._setEncodings(track));
1505
     }
1396
     }
1506
 
1397
 
1507
     return promiseChain;
1398
     return promiseChain;
1531
         return Promise.reject('Stream not found');
1422
         return Promise.reject('Stream not found');
1532
     }
1423
     }
1533
 
1424
 
1534
-    return this.tpcUtils.replaceTrack(null, track).then(() => {
1425
+    return this.replaceTrack(null, track, true /* isMuteOperation */).then(() => {
1535
         if (track) {
1426
         if (track) {
1536
             if (track.isAudioTrack()) {
1427
             if (track.isAudioTrack()) {
1537
                 this._hasHadAudioTrack = true;
1428
                 this._hasHadAudioTrack = true;
1567
     return doesBelong;
1458
     return doesBelong;
1568
 };
1459
 };
1569
 
1460
 
1570
-/**
1571
- * Returns the codec that is configured on the client as the preferred video codec for the given local video track.
1572
- *
1573
- * @param {JitsiLocalTrack} localTrack - The local video track.
1574
- * @returns {CodecMimeType} The codec that is set as the preferred codec for the given local video track.
1575
- *
1576
- */
1577
-TraceablePeerConnection.prototype.getConfiguredVideoCodec = function(localTrack) {
1578
-    const localVideoTrack = localTrack ?? this.getLocalVideoTracks()[0];
1579
-    const rtpSender = this.findSenderForTrack(localVideoTrack.getTrack());
1580
-
1581
-    if (this.usesCodecSelectionAPI() && rtpSender) {
1582
-        const { codecs } = rtpSender.getParameters();
1583
-
1584
-        return codecs[0].mimeType.split('/')[1].toLowerCase();
1585
-    }
1586
-
1587
-    const sdp = this.remoteDescription?.sdp;
1588
-    const defaultCodec = CodecMimeType.VP8;
1589
-
1590
-    if (!sdp) {
1591
-        return defaultCodec;
1592
-    }
1593
-    const parsedSdp = transform.parse(sdp);
1594
-    const mLine = parsedSdp.media
1595
-        .find(m => m.mid.toString() === this._localTrackTransceiverMids.get(localVideoTrack.rtcId));
1596
-    const payload = mLine.payloads.split(' ')[0];
1597
-    const { codec } = mLine.rtp.find(rtp => rtp.payload === Number(payload));
1598
-
1599
-    if (codec) {
1600
-        return Object.values(CodecMimeType).find(value => value === codec.toLowerCase());
1601
-    }
1602
-
1603
-    return defaultCodec;
1604
-};
1605
-
1606
 /**
1461
 /**
1607
  * Returns the codecs in the current order of preference as configured on the peerconnection.
1462
  * Returns the codecs in the current order of preference as configured on the peerconnection.
1608
  *
1463
  *
1610
  * @returns {Array}
1465
  * @returns {Array}
1611
  */
1466
  */
1612
 TraceablePeerConnection.prototype.getConfiguredVideoCodecs = function(description) {
1467
 TraceablePeerConnection.prototype.getConfiguredVideoCodecs = function(description) {
1613
-    const currentSdp = description?.sdp ?? this.localDescription?.sdp;
1614
-
1615
-    if (!currentSdp) {
1616
-        return [];
1617
-    }
1618
-    const parsedSdp = transform.parse(currentSdp);
1619
-    const mLine = parsedSdp.media.find(m => m.type === MediaType.VIDEO);
1620
-    const codecs = new Set(mLine.rtp
1621
-        .filter(pt => pt.codec.toLowerCase() !== 'rtx')
1622
-        .map(pt => pt.codec.toLowerCase()));
1623
-
1624
-    return Array.from(codecs);
1468
+    return this.tpcUtils.getConfiguredVideoCodecs(description);
1625
 };
1469
 };
1626
 
1470
 
1627
 /**
1471
 /**
1725
         const mLines = parsedSdp.media.filter(mline => mline.type === mediaType);
1569
         const mLines = parsedSdp.media.filter(mline => mline.type === mediaType);
1726
 
1570
 
1727
         tracks.forEach((track, idx) => {
1571
         tracks.forEach((track, idx) => {
1728
-            if (!this._localTrackTransceiverMids.has(track.rtcId)) {
1729
-                this._localTrackTransceiverMids.set(track.rtcId, mLines[idx].mid.toString());
1572
+            if (!this.localTrackTransceiverMids.has(track.rtcId)) {
1573
+                this.localTrackTransceiverMids.set(track.rtcId, mLines[idx].mid.toString());
1730
             }
1574
             }
1731
         });
1575
         });
1732
     });
1576
     });
1740
  *
1584
  *
1741
  * @param {JitsiLocalTrack|null} oldTrack - The current track in use to be replaced on the pc.
1585
  * @param {JitsiLocalTrack|null} oldTrack - The current track in use to be replaced on the pc.
1742
  * @param {JitsiLocalTrack|null} newTrack - The new track to be used.
1586
  * @param {JitsiLocalTrack|null} newTrack - The new track to be used.
1743
- *
1587
+ * @param {boolean} isMuteOperation - Whether the operation is a mute/unmute operation.
1744
  * @returns {Promise<boolean>} - If the promise resolves with true, renegotiation will be needed.
1588
  * @returns {Promise<boolean>} - If the promise resolves with true, renegotiation will be needed.
1745
  * Otherwise no renegotiation is needed.
1589
  * Otherwise no renegotiation is needed.
1746
  */
1590
  */
1747
-TraceablePeerConnection.prototype.replaceTrack = function(oldTrack, newTrack) {
1591
+TraceablePeerConnection.prototype.replaceTrack = function(oldTrack, newTrack, isMuteOperation = false) {
1748
     if (!(oldTrack || newTrack)) {
1592
     if (!(oldTrack || newTrack)) {
1749
         logger.info(`${this} replaceTrack called with no new track and no old track`);
1593
         logger.info(`${this} replaceTrack called with no new track and no old track`);
1750
 
1594
 
1753
 
1597
 
1754
     logger.info(`${this} TPC.replaceTrack old=${oldTrack}, new=${newTrack}`);
1598
     logger.info(`${this} TPC.replaceTrack old=${oldTrack}, new=${newTrack}`);
1755
 
1599
 
1756
-    return this.tpcUtils.replaceTrack(oldTrack, newTrack)
1757
-        .then(transceiver => {
1600
+    let transceiver;
1601
+    const mediaType = newTrack?.getType() ?? oldTrack?.getType();
1602
+    const localTracks = this.getLocalTracks(mediaType);
1603
+    const track = newTrack?.getTrack() ?? null;
1604
+    const isNewLocalSource = localTracks?.length
1605
+        && !oldTrack
1606
+        && newTrack
1607
+        && !localTracks.find(t => t === newTrack);
1608
+
1609
+    // If old track exists, replace the track on the corresponding sender.
1610
+    if (oldTrack && !oldTrack.isMuted()) {
1611
+        transceiver = this.peerconnection.getTransceivers().find(t => t.sender.track === oldTrack.getTrack());
1612
+
1613
+    // Find the first recvonly transceiver when more than one track of the same media type is being added to the pc.
1614
+    // As part of the track addition, a new m-line was added to the remote description with direction set to
1615
+    // recvonly.
1616
+    } else if (isNewLocalSource) {
1617
+        transceiver = this.peerconnection.getTransceivers().find(
1618
+            t => t.receiver.track.kind === mediaType
1619
+            && t.direction === MediaDirection.RECVONLY
1620
+
1621
+            // Re-use any existing recvonly transceiver (if available) for p2p case.
1622
+            && ((this.isP2P && t.currentDirection === MediaDirection.RECVONLY)
1623
+                || (t.currentDirection === MediaDirection.INACTIVE && !t.stopped)));
1624
+
1625
+    // For mute/unmute operations, find the transceiver based on the track index in the source name if present,
1626
+    // otherwise it is assumed to be the first local track that was added to the peerconnection.
1627
+    } else {
1628
+        transceiver = this.peerconnection.getTransceivers().find(t => t.receiver.track.kind === mediaType);
1629
+        const sourceName = newTrack?.getSourceName() ?? oldTrack?.getSourceName();
1630
+
1631
+        if (sourceName) {
1632
+            const trackIndex = getSourceIndexFromSourceName(sourceName);
1633
+
1634
+            if (this.isP2P) {
1635
+                transceiver = this.peerconnection.getTransceivers()
1636
+                    .filter(t => t.receiver.track.kind === mediaType)[trackIndex];
1637
+            } else if (oldTrack) {
1638
+                const transceiverMid = this.localTrackTransceiverMids.get(oldTrack.rtcId);
1639
+
1640
+                transceiver = this.peerconnection.getTransceivers().find(t => t.mid === transceiverMid);
1641
+            } else if (trackIndex) {
1642
+                transceiver = this.peerconnection.getTransceivers()
1643
+                        .filter(t => t.receiver.track.kind === mediaType
1644
+                            && t.direction !== MediaDirection.RECVONLY)[trackIndex];
1645
+            }
1646
+        }
1647
+    }
1648
+
1649
+    if (!transceiver) {
1650
+        return Promise.reject(
1651
+            new Error(`Replace track failed - no transceiver for old: ${oldTrack}, new: ${newTrack}`));
1652
+    }
1653
+
1654
+    return transceiver.sender.replaceTrack(track)
1655
+        .then(() => {
1656
+            if (isMuteOperation) {
1657
+                return Promise.resolve();
1658
+            }
1758
             if (oldTrack) {
1659
             if (oldTrack) {
1759
                 this.localTracks.delete(oldTrack.rtcId);
1660
                 this.localTracks.delete(oldTrack.rtcId);
1760
-                this._localTrackTransceiverMids.delete(oldTrack.rtcId);
1661
+                this.localTrackTransceiverMids.delete(oldTrack.rtcId);
1761
             }
1662
             }
1762
 
1663
 
1763
             if (newTrack) {
1664
             if (newTrack) {
1766
                 } else {
1667
                 } else {
1767
                     this._hasHadVideoTrack = true;
1668
                     this._hasHadVideoTrack = true;
1768
                 }
1669
                 }
1769
-                this._localTrackTransceiverMids.set(newTrack.rtcId, transceiver?.mid?.toString());
1670
+                this.localTrackTransceiverMids.set(newTrack.rtcId, transceiver?.mid?.toString());
1770
                 this.localTracks.set(newTrack.rtcId, newTrack);
1671
                 this.localTracks.set(newTrack.rtcId, newTrack);
1771
             }
1672
             }
1772
 
1673
 
1804
             const configureEncodingsPromise
1705
             const configureEncodingsPromise
1805
                 = !newTrack || browser.usesSdpMungingForSimulcast()
1706
                 = !newTrack || browser.usesSdpMungingForSimulcast()
1806
                     ? Promise.resolve()
1707
                     ? Promise.resolve()
1807
-                    : this.tpcUtils.setEncodings(newTrack);
1708
+                    : this._setEncodings(newTrack);
1808
 
1709
 
1809
             return configureEncodingsPromise.then(() => this.isP2P);
1710
             return configureEncodingsPromise.then(() => this.isP2P);
1810
         });
1711
         });
1827
         return Promise.reject('Track not found in the peerconnection');
1728
         return Promise.reject('Track not found in the peerconnection');
1828
     }
1729
     }
1829
 
1730
 
1830
-    return this.tpcUtils.replaceTrack(localTrack, null).then(() => false);
1731
+    return this.replaceTrack(localTrack, null, true /* isMuteOperation */).then(() => false);
1831
 };
1732
 };
1832
 
1733
 
1833
 /**
1734
 /**
1937
     return defaultCodec;
1838
     return defaultCodec;
1938
 };
1839
 };
1939
 
1840
 
1940
-/**
1941
- * Munges the stereo flag as well as the opusMaxAverageBitrate in the SDP, based
1942
- * on values set through config.js, if present.
1943
- *
1944
- * @param {RTCSessionDescription} description that needs to be munged.
1945
- * @returns {RTCSessionDescription} the munged description.
1946
- */
1947
-TraceablePeerConnection.prototype._mungeOpus = function(description) {
1948
-    const { audioQuality } = this.options;
1949
-
1950
-    if (!audioQuality?.enableOpusDtx && !audioQuality?.stereo && !audioQuality?.opusMaxAverageBitrate) {
1951
-        return description;
1952
-    }
1953
-
1954
-    const parsedSdp = transform.parse(description.sdp);
1955
-    const mLines = parsedSdp.media;
1956
-
1957
-    for (const mLine of mLines) {
1958
-        if (mLine.type === 'audio') {
1959
-            const { payload } = mLine.rtp.find(protocol => protocol.codec === CodecMimeType.OPUS);
1960
-
1961
-            if (!payload) {
1962
-                // eslint-disable-next-line no-continue
1963
-                continue;
1964
-            }
1965
-
1966
-            let fmtpOpus = mLine.fmtp.find(protocol => protocol.payload === payload);
1967
-
1968
-            if (!fmtpOpus) {
1969
-                fmtpOpus = {
1970
-                    payload,
1971
-                    config: ''
1972
-                };
1973
-            }
1974
-
1975
-            const fmtpConfig = transform.parseParams(fmtpOpus.config);
1976
-            let sdpChanged = false;
1977
-
1978
-            if (audioQuality?.stereo) {
1979
-                fmtpConfig.stereo = 1;
1980
-                sdpChanged = true;
1981
-            }
1982
-
1983
-            if (audioQuality?.opusMaxAverageBitrate) {
1984
-                fmtpConfig.maxaveragebitrate = audioQuality.opusMaxAverageBitrate;
1985
-                sdpChanged = true;
1986
-            }
1987
-
1988
-            // On Firefox, the OpusDtx enablement has no effect
1989
-            if (!browser.isFirefox() && audioQuality?.enableOpusDtx) {
1990
-                fmtpConfig.usedtx = 1;
1991
-                sdpChanged = true;
1992
-            }
1993
-
1994
-            if (!sdpChanged) {
1995
-                // eslint-disable-next-line no-continue
1996
-                continue;
1997
-            }
1998
-
1999
-            let mungedConfig = '';
2000
-
2001
-            for (const key of Object.keys(fmtpConfig)) {
2002
-                mungedConfig += `${key}=${fmtpConfig[key]}; `;
2003
-            }
2004
-
2005
-            fmtpOpus.config = mungedConfig.trim();
2006
-        }
2007
-    }
2008
-
2009
-    return new RTCSessionDescription({
2010
-        type: description.type,
2011
-        sdp: transform.write(parsedSdp)
2012
-    });
2013
-};
2014
-
2015
 /**
1841
 /**
2016
  * Sets up the _dtlsTransport object and initializes callbacks for it.
1842
  * Sets up the _dtlsTransport object and initializes callbacks for it.
2017
  */
1843
  */
2036
     }
1862
     }
2037
 };
1863
 };
2038
 
1864
 
2039
-/**
2040
- * Sets the max bitrates on the video m-lines when VP9/AV1 is the selected codec.
2041
- *
2042
- * @param {RTCSessionDescription} description - The local description that needs to be munged.
2043
- * @param {boolean} isLocalSdp - Whether the max bitrate (via b=AS line in SDP) is set on local SDP.
2044
- * @returns RTCSessionDescription
2045
- */
2046
-TraceablePeerConnection.prototype._setMaxBitrates = function(description, isLocalSdp = false) {
2047
-    if (!this.codecSettings) {
2048
-        return description;
2049
-    }
2050
-    const parsedSdp = transform.parse(description.sdp);
2051
-
2052
-    // Find all the m-lines associated with the local sources.
2053
-    const direction = isLocalSdp ? MediaDirection.RECVONLY : MediaDirection.SENDONLY;
2054
-    const mLines = parsedSdp.media.filter(m => m.type === MediaType.VIDEO && m.direction !== direction);
2055
-    const currentCodec = this.codecSettings.codecList[0];
2056
-    const codecScalabilityModeSettings = this.tpcUtils.codecSettings[currentCodec];
2057
-
2058
-    for (const mLine of mLines) {
2059
-        const isDoingVp9KSvc = currentCodec === CodecMimeType.VP9
2060
-            && !codecScalabilityModeSettings.scalabilityModeEnabled;
2061
-        const localTrack = this.getLocalVideoTracks()
2062
-            .find(track => this._localTrackTransceiverMids.get(track.rtcId) === mLine.mid.toString());
2063
-
2064
-        if (localTrack
2065
-            && (isDoingVp9KSvc
2066
-
2067
-                // Setting bitrates in the SDP for SVC codecs is no longer needed in the newer versions where
2068
-                // maxBitrates from the RTCRtpEncodingParameters directly affect the target bitrate for the encoder.
2069
-                || (this.tpcUtils._isRunningInFullSvcMode(currentCodec) && !this.usesCodecSelectionAPI()))) {
2070
-            let maxBitrate;
2071
-
2072
-            if (localTrack.getVideoType() === VideoType.DESKTOP) {
2073
-                maxBitrate = codecScalabilityModeSettings.maxBitratesVideo.ssHigh;
2074
-            } else {
2075
-                const { level } = VIDEO_QUALITY_LEVELS.find(lvl => lvl.height <= localTrack.getCaptureResolution());
2076
-
2077
-                maxBitrate = codecScalabilityModeSettings.maxBitratesVideo[level];
2078
-            }
2079
-
2080
-            const limit = Math.floor(maxBitrate / 1000);
2081
-
2082
-            // Use only the highest spatial layer bitrates for now as there is no API available yet for configuring
2083
-            // the bitrates on the individual SVC layers.
2084
-            mLine.bandwidth = [ {
2085
-                type: 'AS',
2086
-                limit
2087
-            } ];
2088
-        } else {
2089
-            // Clear the bandwidth limit in SDP when VP9 is no longer the preferred codec.
2090
-            // This is needed on react native clients as react-native-webrtc returns the
2091
-            // SDP that the application passed instead of returning the SDP off the native side.
2092
-            // This line automatically gets cleared on web on every renegotiation.
2093
-            mLine.bandwidth = undefined;
2094
-        }
2095
-    }
2096
-
2097
-    return new RTCSessionDescription({
2098
-        type: description.type,
2099
-        sdp: transform.write(parsedSdp)
2100
-    });
2101
-};
2102
-
2103
 /**
1865
 /**
2104
  * Returns the expected send resolution for a local video track based on what encodings are currently active.
1866
  * Returns the expected send resolution for a local video track based on what encodings are currently active.
2105
  *
1867
  *
2144
  */
1906
  */
2145
 TraceablePeerConnection.prototype.configureAudioSenderEncodings = function(localAudioTrack = null) {
1907
 TraceablePeerConnection.prototype.configureAudioSenderEncodings = function(localAudioTrack = null) {
2146
     if (localAudioTrack) {
1908
     if (localAudioTrack) {
2147
-        return this.tpcUtils.setEncodings(localAudioTrack);
1909
+        return this._setEncodings(localAudioTrack);
2148
     }
1910
     }
2149
     const promises = [];
1911
     const promises = [];
2150
 
1912
 
2151
     for (const track of this.getLocalTracks(MediaType.AUDIO)) {
1913
     for (const track of this.getLocalTracks(MediaType.AUDIO)) {
2152
-        promises.push(this.tpcUtils.setEncodings(track));
1914
+        promises.push(this._setEncodings(track));
2153
     }
1915
     }
2154
 
1916
 
2155
     return Promise.allSettled(promises);
1917
     return Promise.allSettled(promises);
2156
 };
1918
 };
2157
 
1919
 
1920
+/**
1921
+ * Configures the RTCRtpEncodingParameters of the outbound rtp stream associated with the given track.
1922
+ *
1923
+ * @param {JitsiLocalTracj} localTrack - The local track whose outbound stream needs to be configured.
1924
+ * @returns {Promise} - A promise that resolves when the operation is successful, rejected otherwise.
1925
+ */
1926
+TraceablePeerConnection.prototype._configureSenderEncodings = function(localTrack) {
1927
+    const mediaType = localTrack.getType();
1928
+    const transceiver = localTrack?.track && localTrack.getOriginalStream()
1929
+        ? this.peerconnection.getTransceivers().find(t => t.sender?.track?.id === localTrack.getTrackId())
1930
+        : this.peerconnection.getTransceivers().find(t => t.receiver?.track?.kind === mediaType);
1931
+    const parameters = transceiver?.sender?.getParameters();
1932
+
1933
+    // Resolve if the encodings are not available yet. This happens immediately after the track is added to the
1934
+    // peerconnection on chrome in unified-plan. It is ok to ignore and not report the error here since the
1935
+    // action that triggers 'addTrack' (like unmute) will also configure the encodings and set bitrates after that.
1936
+    if (!parameters?.encodings?.length) {
1937
+        return Promise.resolve();
1938
+    }
1939
+
1940
+    parameters.encodings = this.tpcUtils.getStreamEncodings(localTrack);
1941
+
1942
+    return transceiver.sender.setParameters(parameters);
1943
+};
1944
+
1945
+/**
1946
+ * Enables/disables the streams by changing the active field on RTCRtpEncodingParameters for a given RTCRtpSender.
1947
+ *
1948
+ * @param {RTCRtpSender} sender - the sender associated with a MediaStreamTrack.
1949
+ * @param {boolean} enable - whether the streams needs to be enabled or disabled.
1950
+ * @returns {Promise} - A promise that resolves when the operation is successful, rejected otherwise.
1951
+ */
1952
+TraceablePeerConnection.prototype._enableSenderEncodings = function(sender, enable) {
1953
+    const parameters = sender.getParameters();
1954
+
1955
+    if (parameters?.encodings?.length) {
1956
+        for (const encoding of parameters.encodings) {
1957
+            encoding.active = enable;
1958
+        }
1959
+    }
1960
+
1961
+    return sender.setParameters(parameters);
1962
+};
1963
+
2158
 /**
1964
 /**
2159
  * Configures the stream encodings depending on the video type, scalability mode and the bitrate settings for the codec
1965
  * Configures the stream encodings depending on the video type, scalability mode and the bitrate settings for the codec
2160
  * that is currently selected.
1966
  * that is currently selected.
2182
     return Promise.allSettled(promises);
1988
     return Promise.allSettled(promises);
2183
 };
1989
 };
2184
 
1990
 
2185
-TraceablePeerConnection.prototype.setLocalDescription = function(description) {
2186
-    let localDescription = description;
1991
+/**
1992
+ * Set the simulcast stream encoding properties on the RTCRtpSender.
1993
+ *
1994
+ * @param {JitsiLocalTrack} localTrack - the current track in use for which the encodings are to be set.
1995
+ * @returns {Promise<void>} - resolved when done.
1996
+ */
1997
+TraceablePeerConnection.prototype._setEncodings = function(localTrack) {
1998
+    if (localTrack.getType() === MediaType.VIDEO) {
1999
+        return this._updateVideoSenderParameters(() => this._configureSenderEncodings(localTrack));
2000
+    }
2187
 
2001
 
2188
-    this.trace('setLocalDescription::preTransform', dumpSDP(localDescription));
2002
+    return this._configureSenderEncodings(localTrack);
2003
+};
2189
 
2004
 
2190
-    // Munge stereo flag and opusMaxAverageBitrate based on config.js
2191
-    localDescription = this._mungeOpus(localDescription);
2005
+/**
2006
+ * Munges the provided description to update the codec order, set the max bitrates (for VP9 K-SVC), set stereo flag
2007
+ * and update the DD Header extensions for AV1.
2008
+ *
2009
+ * @param {RTCSessionDescription} description - The description to be munged.
2010
+ * @returns {RTCSessionDescription} - The munged description.
2011
+ */
2012
+TraceablePeerConnection.prototype._mungeDescription = function(description) {
2013
+    let mungedDescription = description;
2014
+
2015
+    this.trace('RTCSessionDescription::preTransform', dumpSDP(description));
2016
+    mungedDescription = this.tpcUtils.mungeOpus(description);
2017
+    mungedDescription = this.tpcUtils.mungeCodecOrder(mungedDescription);
2018
+    mungedDescription = this.tpcUtils.setMaxBitrates(mungedDescription, true);
2019
+    mungedDescription = this.tpcUtils.updateAv1DdHeaders(mungedDescription);
2020
+    this.trace('RTCSessionDescription::postTransform', dumpSDP(mungedDescription));
2021
+
2022
+    return mungedDescription;
2023
+};
2192
 
2024
 
2193
-    // Munge the order of the codecs based on the preferences set through config.js.
2194
-    localDescription = this._mungeCodecOrder(localDescription);
2195
-    localDescription = this._setMaxBitrates(localDescription, true);
2196
-    localDescription = this._updateAv1DdHeaders(localDescription);
2025
+/**
2026
+ * Sets the local description on the peerconnection.
2027
+ *
2028
+ * @param {RTCSessionDescription} description - The local description to be set.
2029
+ * @returns {Promise<void>} - Resolved when the operation is successful and rejected with an error otherwise.
2030
+ */
2031
+TraceablePeerConnection.prototype.setLocalDescription = function(description) {
2032
+    let localDescription = description;
2197
 
2033
 
2198
-    this.trace('setLocalDescription::postTransform', dumpSDP(localDescription));
2034
+    localDescription = this._mungeDescription(localDescription);
2199
 
2035
 
2200
     return new Promise((resolve, reject) => {
2036
     return new Promise((resolve, reject) => {
2201
         this.peerconnection.setLocalDescription(localDescription)
2037
         this.peerconnection.setLocalDescription(localDescription)
2219
     });
2055
     });
2220
 };
2056
 };
2221
 
2057
 
2058
+/**
2059
+ * Sets the remote description on the peerconnection.
2060
+ *
2061
+ * @param {RTCSessionDescription} description - The remote description to be set.
2062
+ * @returns {Promise<void>} - Resolved when the operation is successful and rejected with an error otherwise.
2063
+ */
2222
 TraceablePeerConnection.prototype.setRemoteDescription = function(description) {
2064
 TraceablePeerConnection.prototype.setRemoteDescription = function(description) {
2223
     let remoteDescription = description;
2065
     let remoteDescription = description;
2224
 
2066
 
2225
-    this.trace('setRemoteDescription::preTransform', dumpSDP(description));
2226
-
2227
-    // Munge stereo flag and opusMaxAverageBitrate based on config.js
2228
-    remoteDescription = this._mungeOpus(remoteDescription);
2229
-
2230
     if (this.isSpatialScalabilityOn()) {
2067
     if (this.isSpatialScalabilityOn()) {
2231
         remoteDescription = this.tpcUtils.insertUnifiedPlanSimulcastReceive(remoteDescription);
2068
         remoteDescription = this.tpcUtils.insertUnifiedPlanSimulcastReceive(remoteDescription);
2232
         this.trace('setRemoteDescription::postTransform (sim receive)', dumpSDP(remoteDescription));
2069
         this.trace('setRemoteDescription::postTransform (sim receive)', dumpSDP(remoteDescription));
2234
     remoteDescription = this.tpcUtils.ensureCorrectOrderOfSsrcs(remoteDescription);
2071
     remoteDescription = this.tpcUtils.ensureCorrectOrderOfSsrcs(remoteDescription);
2235
     this.trace('setRemoteDescription::postTransform (correct ssrc order)', dumpSDP(remoteDescription));
2072
     this.trace('setRemoteDescription::postTransform (correct ssrc order)', dumpSDP(remoteDescription));
2236
 
2073
 
2237
-    // Munge the order of the codecs based on the preferences set through config.js.
2238
-    remoteDescription = this._mungeCodecOrder(remoteDescription);
2239
-    remoteDescription = this._setMaxBitrates(remoteDescription);
2240
-    remoteDescription = this._updateAv1DdHeaders(remoteDescription);
2241
-    this.trace('setRemoteDescription::postTransform (munge codec order)', dumpSDP(remoteDescription));
2074
+    remoteDescription = this._mungeDescription(remoteDescription);
2242
 
2075
 
2243
     return new Promise((resolve, reject) => {
2076
     return new Promise((resolve, reject) => {
2244
         this.peerconnection.setRemoteDescription(remoteDescription)
2077
         this.peerconnection.setRemoteDescription(remoteDescription)
2343
     parameters.degradationPreference = preference;
2176
     parameters.degradationPreference = preference;
2344
 
2177
 
2345
     // Calculate the encodings active state based on the resolution requested by the bridge.
2178
     // Calculate the encodings active state based on the resolution requested by the bridge.
2346
-    const codecForCamera = preferredCodec ?? this.getConfiguredVideoCodec(localVideoTrack);
2179
+    const codecForCamera = preferredCodec ?? this.tpcUtils.getConfiguredVideoCodec(localVideoTrack);
2347
     const codec = isScreensharingTrack ? this._getPreferredCodecForScreenshare(codecForCamera) : codecForCamera;
2180
     const codec = isScreensharingTrack ? this._getPreferredCodecForScreenshare(codecForCamera) : codecForCamera;
2348
     const activeState = this.tpcUtils.calculateEncodingsActiveState(localVideoTrack, codec, frameHeight);
2181
     const activeState = this.tpcUtils.calculateEncodingsActiveState(localVideoTrack, codec, frameHeight);
2349
     let bitrates = this.tpcUtils.calculateEncodingsBitrates(localVideoTrack, codec, frameHeight);
2182
     let bitrates = this.tpcUtils.calculateEncodingsBitrates(localVideoTrack, codec, frameHeight);
2435
     });
2268
     });
2436
 };
2269
 };
2437
 
2270
 
2271
+/**
2272
+ * Resumes or suspends media on the peerconnection by setting the active state on RTCRtpEncodingParameters
2273
+ * associated with all the senders that have a track attached to it.
2274
+ *
2275
+ * @param {boolean} enable - whether outgoing media needs to be enabled or disabled.
2276
+ * @param {string} mediaType - media type, 'audio' or 'video', if neither is passed, all outgoing media will either
2277
+ * be enabled or disabled.
2278
+ * @returns {Promise} - A promise that is resolved when the change is succesful on all the senders, rejected
2279
+ * otherwise.
2280
+ */
2281
+TraceablePeerConnection.prototype.setMediaTransferActive = function(enable, mediaType) {
2282
+    logger.info(`${this} ${enable ? 'Resuming' : 'Suspending'} media transfer.`);
2283
+
2284
+    const senders = this.peerconnection.getSenders()
2285
+        .filter(s => Boolean(s.track) && (!mediaType || s.track.kind === mediaType));
2286
+    const promises = [];
2287
+
2288
+    for (const sender of senders) {
2289
+        if (sender.track.kind === MediaType.VIDEO) {
2290
+            promises.push(this._updateVideoSenderParameters(() => this._enableSenderEncodings(sender, enable)));
2291
+        } else {
2292
+            promises.push(this._enableSenderEncodings(sender, enable));
2293
+        }
2294
+    }
2295
+
2296
+    return Promise.allSettled(promises)
2297
+        .then(settledResult => {
2298
+            const errors = settledResult
2299
+                .filter(result => result.status === 'rejected')
2300
+                .map(result => result.reason);
2301
+
2302
+            if (errors.length) {
2303
+                return Promise.reject(new Error('Failed to change encodings on the RTCRtpSenders'
2304
+                    + `${errors.join(' ')}`));
2305
+            }
2306
+
2307
+            return Promise.resolve();
2308
+        });
2309
+};
2310
+
2438
 /**
2311
 /**
2439
  * Enables/disables outgoing video media transmission on this peer connection. When disabled the stream encoding's
2312
  * Enables/disables outgoing video media transmission on this peer connection. When disabled the stream encoding's
2440
  * active state is enabled or disabled to send or stop the media.
2313
  * active state is enabled or disabled to send or stop the media.
2450
     this.videoTransferActive = active;
2323
     this.videoTransferActive = active;
2451
 
2324
 
2452
     if (changed) {
2325
     if (changed) {
2453
-        return this.tpcUtils.setMediaTransferActive(active, MediaType.VIDEO);
2326
+        return this.setMediaTransferActive(active, MediaType.VIDEO);
2454
     }
2327
     }
2455
 
2328
 
2456
     return Promise.resolve();
2329
     return Promise.resolve();

+ 1
- 1
modules/xmpp/JingleSessionPC.js Parādīt failu

2103
             return Promise.resolve();
2103
             return Promise.resolve();
2104
         }
2104
         }
2105
 
2105
 
2106
-        return this.peerconnection.tpcUtils.setMediaTransferActive(active)
2106
+        return this.peerconnection.setMediaTransferActive(active)
2107
             .then(() => {
2107
             .then(() => {
2108
                 this.peerconnection.audioTransferActive = active;
2108
                 this.peerconnection.audioTransferActive = active;
2109
                 this.peerconnection.videoTransferActive = active;
2109
                 this.peerconnection.videoTransferActive = active;

+ 0
- 1
types/hand-crafted/modules/RTC/TPCUtils.d.ts Parādīt failu

9
   getLocalStreamHeightConstraints: ( localTrack: JitsiLocalTrack ) => number[];
9
   getLocalStreamHeightConstraints: ( localTrack: JitsiLocalTrack ) => number[];
10
   removeTrackMute: ( localTrack: JitsiLocalTrack ) => Promise<void>;
10
   removeTrackMute: ( localTrack: JitsiLocalTrack ) => Promise<void>;
11
   replaceTrack: ( oldTrack: JitsiLocalTrack, newTrack: JitsiLocalTrack ) => Promise<void>;
11
   replaceTrack: ( oldTrack: JitsiLocalTrack, newTrack: JitsiLocalTrack ) => Promise<void>;
12
-  setEncodings: ( track: JitsiLocalTrack ) => Promise<void>;
13
   setMediaTransferActive: ( active: boolean ) => void;
12
   setMediaTransferActive: ( active: boolean ) => void;
14
   setVideoTransferActive: ( active: boolean ) => void;
13
   setVideoTransferActive: ( active: boolean ) => void;
15
   updateEncodingsResolution: ( parameters: RTCRtpEncodingParameters ) => void;
14
   updateEncodingsResolution: ( parameters: RTCRtpEncodingParameters ) => void;

Notiek ielāde…
Atcelt
Saglabāt