Ver código fonte

feat(video-quality): control the sender resolution based on video quality settings (#1119)

* feat(video-quality): control the sender resolution based on video quality settings

* fix(video-quality): Apply the settings on newly created p2p/jvb jingle sessions
If a p2p/jvb session is not present when setSenderVideoConstraint is called,
make sure the settings are applied when they are created
dev1
Jaya Allamsetty 5 anos atrás
pai
commit
47b292e332
Nenhuma conta vinculada ao e-mail do autor do commit

+ 48
- 0
JitsiConference.js Ver arquivo

@@ -235,6 +235,12 @@ export default function JitsiConference(options) {
235 235
     this.recordingManager = new RecordingManager(this.room);
236 236
     this._conferenceJoinAnalyticsEventSent = false;
237 237
 
238
+    /**
239
+     * Max frame height that the user prefers to send to the remote participants.
240
+     * @type {number}
241
+     */
242
+    this.maxFrameHeight = null;
243
+
238 244
     if (browser.supportsInsertableStreams()) {
239 245
         this._e2eeCtx = new E2EEContext({ salt: this.options.name });
240 246
     }
@@ -1879,6 +1885,13 @@ JitsiConference.prototype._acceptJvbIncomingCall = function(
1879 1885
                 // to be turned off here.
1880 1886
                 if (this.isP2PActive() && this.jvbJingleSession) {
1881 1887
                     this._suspendMediaTransferForJvbConnection();
1888
+                } else if (this.jvbJingleSession && this.maxFrameHeight) {
1889
+                    // Apply user preferred max frame height if it was called before this
1890
+                    // jingle session was created.
1891
+                    this.jvbJingleSession.setSenderVideoConstraint(this.maxFrameHeight)
1892
+                        .catch(err => {
1893
+                            logger.error(`Sender video constraints failed on jvb session - ${err}`);
1894
+                        });
1882 1895
                 }
1883 1896
 
1884 1897
                 // Setup E2EE.
@@ -2644,6 +2657,15 @@ JitsiConference.prototype._acceptP2PIncomingCall = function(
2644 2657
         () => {
2645 2658
             logger.debug('Got RESULT for P2P "session-accept"');
2646 2659
 
2660
+            // Apply user preferred max frame height if it was called before this
2661
+            // jingle session was created.
2662
+            if (this.pendingVideoConstraintsOnP2P) {
2663
+                this.p2pJingleSession.setSenderVideoConstraint(this.maxFrameHeight)
2664
+                    .catch(err => {
2665
+                        logger.error(`Sender video constraints failed on p2p session - ${err}`);
2666
+                    });
2667
+            }
2668
+
2647 2669
             // Setup E2EE.
2648 2670
             for (const track of localTracks) {
2649 2671
                 this._setupSenderE2EEForTrack(jingleSession, track);
@@ -3261,6 +3283,32 @@ JitsiConference.prototype.setReceiverVideoConstraint = function(
3261 3283
     this.rtc.setReceiverVideoConstraint(maxFrameHeight);
3262 3284
 };
3263 3285
 
3286
+/**
3287
+ * Sets the maximum video size the local participant should send to remote
3288
+ * participants.
3289
+ * @param {number} maxFrameHeight - The user preferred max frame height.
3290
+ * @returns {Promise} promise that will be resolved when the operation is
3291
+ * successful and rejected otherwise.
3292
+ */
3293
+JitsiConference.prototype.setSenderVideoConstraint = function(maxFrameHeight) {
3294
+    this.maxFrameHeight = maxFrameHeight;
3295
+    this.pendingVideoConstraintsOnP2P = true;
3296
+    const promises = [];
3297
+
3298
+    // We have to always set the sender video constraints on the jvb connection
3299
+    // when we switch from p2p to jvb connection since we need to check if the
3300
+    // tracks constraints have been modified when in p2p.
3301
+    if (this.jvbJingleSession) {
3302
+        promises.push(this.jvbJingleSession.setSenderVideoConstraint(maxFrameHeight));
3303
+    }
3304
+    if (this.p2pJingleSession) {
3305
+        this.pendingVideoConstraintsOnP2P = false;
3306
+        promises.push(this.p2pJingleSession.setSenderVideoConstraint(maxFrameHeight));
3307
+    }
3308
+
3309
+    return Promise.all(promises);
3310
+};
3311
+
3264 3312
 /**
3265 3313
  * Creates a video SIP GW session and returns it if service is enabled. Before
3266 3314
  * creating a session one need to check whether video SIP GW service is

+ 3
- 1
doc/API.md Ver arquivo

@@ -406,7 +406,9 @@ Throws NetworkError or InvalidStateError or Error if the operation fails.
406 406
 34. setReceiverVideoConstraint(resolution) - set the desired resolution to get from JVB (180, 360, 720, 1080, etc).
407 407
     You should use that method if you are using simulcast.
408 408
 
409
-35. isHidden - checks if local user has joined as a "hidden" user. This is a specialized role used for integrations.
409
+35. setSenderVideoConstraint(resolution) - set the desired resolution to send to JVB or the peer (180, 360, 720).
410
+
411
+36. isHidden - checks if local user has joined as a "hidden" user. This is a specialized role used for integrations.
410 412
 
411 413
 JitsiTrack
412 414
 ======

+ 1
- 1
modules/RTC/JitsiLocalTrack.js Ver arquivo

@@ -551,7 +551,7 @@ export default class JitsiLocalTrack extends JitsiTrack {
551 551
                     = RTCUtils.obtainAudioAndVideoPermissions(streamOptions);
552 552
             }
553 553
 
554
-            promise.then(streamsInfo => {
554
+            promise = promise.then(streamsInfo => {
555 555
                 // The track kind for presenter track is video as well.
556 556
                 const mediaType = this.getType() === MediaType.PRESENTER ? MediaType.VIDEO : this.getType();
557 557
                 const streamInfo

+ 31
- 0
modules/RTC/TPCUtils.js Ver arquivo

@@ -4,6 +4,7 @@ import transform from 'sdp-transform';
4 4
 import * as JitsiTrackEvents from '../../JitsiTrackEvents';
5 5
 import browser from '../browser';
6 6
 import RTCEvents from '../../service/RTC/RTCEvents';
7
+import * as VideoType from '../../service/RTC/VideoType';
7 8
 
8 9
 const logger = getLogger(__filename);
9 10
 const SIM_LAYER_1_RID = '1';
@@ -47,6 +48,12 @@ export class TPCUtils {
47 48
                 scaleResolutionDownBy: browser.isFirefox() ? 4.0 : 1.0
48 49
             }
49 50
         ];
51
+
52
+        /**
53
+         * Resolution height constraints for the simulcast encodings that
54
+         * are configured for the video tracks.
55
+         */
56
+        this.simulcastStreamConstraints = [];
50 57
     }
51 58
 
52 59
     /**
@@ -168,6 +175,25 @@ export class TPCUtils {
168 175
         });
169 176
     }
170 177
 
178
+    /**
179
+     * Constructs resolution height constraints for the simulcast encodings that are
180
+     * created for a given local video track.
181
+     * @param {MediaStreamTrack} track - the local video track.
182
+     * @returns {void}
183
+     */
184
+    _setSimulcastStreamConstraints(track) {
185
+        const height = track.getSettings().height;
186
+
187
+        for (const encoding in this.simulcastEncodings) {
188
+            if (this.simulcastEncodings.hasOwnProperty(encoding)) {
189
+                this.simulcastStreamConstraints.push({
190
+                    height: height / this.simulcastEncodings[encoding].scaleResolutionDownBy,
191
+                    rid: this.simulcastEncodings[encoding].rid
192
+                });
193
+            }
194
+        }
195
+    }
196
+
171 197
     /**
172 198
     * Adds {@link JitsiLocalTrack} to the WebRTC peerconnection for the first time.
173 199
     * @param {JitsiLocalTrack} track - track to be added to the peerconnection.
@@ -196,6 +222,11 @@ export class TPCUtils {
196 222
             // unused "recv-only" transceiver.
197 223
             this.pc.peerconnection.addTrack(track);
198 224
         }
225
+
226
+        // Construct the simulcast stream constraints for the newly added track.
227
+        if (localTrack.isVideoTrack() && localTrack.videoType === VideoType.CAMERA && this.pc.isSimulcastOn()) {
228
+            this._setSimulcastStreamConstraints(localTrack.getTrack());
229
+        }
199 230
     }
200 231
 
201 232
     /**

+ 106
- 0
modules/RTC/TraceablePeerConnection.js Ver arquivo

@@ -6,7 +6,12 @@ import transform from 'sdp-transform';
6 6
 
7 7
 import * as GlobalOnErrorHandler from '../util/GlobalOnErrorHandler';
8 8
 import JitsiRemoteTrack from './JitsiRemoteTrack';
9
+import {
10
+    TRACK_ADDED,
11
+    TRACK_MUTE_CHANGED
12
+} from '../../JitsiConferenceEvents';
9 13
 import * as MediaType from '../../service/RTC/MediaType';
14
+import * as VideoType from '../../service/RTC/VideoType';
10 15
 import LocalSdpMunger from './LocalSdpMunger';
11 16
 import RTC from './RTC';
12 17
 import RTCUtils from './RTCUtils';
@@ -335,6 +340,29 @@ export default function TraceablePeerConnection(
335 340
         }, 1000);
336 341
     }
337 342
 
343
+    // Set sender video constraints when a new local video track is added
344
+    // to the conference or when it is unmuted.
345
+    this.senderVideoMaxHeight = null;
346
+    const maybeSetSenderVideoConstraints = track => {
347
+        if (track.isLocal()
348
+            && !track.isMuted()
349
+            && track.isVideoTrack()
350
+            && track.videoType === VideoType.CAMERA
351
+            && this.senderVideoMaxHeight) {
352
+            this.setSenderVideoConstraint(this.senderVideoMaxHeight)
353
+                .catch(err => {
354
+                    logger.error(`Settings sender video constraints failed: ${err}`);
355
+                });
356
+        }
357
+    };
358
+
359
+    this.rtc.conference.on(
360
+        TRACK_ADDED,
361
+        maybeSetSenderVideoConstraints);
362
+    this.rtc.conference.on(
363
+        TRACK_MUTE_CHANGED,
364
+        maybeSetSenderVideoConstraints);
365
+
338 366
     logger.info(`Create new ${this}`);
339 367
 }
340 368
 
@@ -1469,6 +1497,11 @@ TraceablePeerConnection.prototype.addTrack = function(track, isInitiator = false
1469 1497
     if (browser.usesUnifiedPlan() && !browser.usesSdpMungingForSimulcast()) {
1470 1498
         this.tpcUtils.setEncodings(track);
1471 1499
     }
1500
+
1501
+    // Construct the simulcast stream constraints for the newly added track.
1502
+    if (track.isVideoTrack() && track.videoType === VideoType.CAMERA && this.isSimulcastOn()) {
1503
+        this.tpcUtils._setSimulcastStreamConstraints(track.getTrack());
1504
+    }
1472 1505
 };
1473 1506
 
1474 1507
 /**
@@ -2042,6 +2075,79 @@ TraceablePeerConnection.prototype.setRemoteDescription = function(description) {
2042 2075
     });
2043 2076
 };
2044 2077
 
2078
+/**
2079
+ * Changes the resolution of the video stream that is sent to the peer based on
2080
+ * the user preferred value. If simulcast is enabled on the peerconection, all the
2081
+ * simulcast encodings that have a resolution height lower or equal to the value
2082
+ * provided will remain active. For the non-simulcast case, video constraint is
2083
+ * applied on the track.
2084
+ * @param {number} frameHeight - The user preferred max frame height.
2085
+ * @returns {Promise} promise that will be resolved when the operation is
2086
+ * successful and rejected otherwise.
2087
+ */
2088
+TraceablePeerConnection.prototype.setSenderVideoConstraint = function(frameHeight) {
2089
+    this.senderVideoMaxHeight = frameHeight;
2090
+    const localVideoTrack = Array.from(this.localTracks.values()).find(t => t.isVideoTrack());
2091
+
2092
+    if (!localVideoTrack || localVideoTrack.isMuted() || localVideoTrack.videoType !== VideoType.CAMERA) {
2093
+        return Promise.resolve();
2094
+    }
2095
+    const track = localVideoTrack.getTrack();
2096
+
2097
+    if (this.isSimulcastOn()) {
2098
+        let promise = Promise.resolve();
2099
+
2100
+        // Check if the track constraints have been modified in p2p mode, apply
2101
+        // the constraints that were used for creating the track if that is the case.
2102
+        const height = localVideoTrack._constraints.height.ideal
2103
+            ? localVideoTrack._constraints.height.ideal
2104
+            : localVideoTrack._constraints.height;
2105
+
2106
+        if (track.getSettings().height !== height) {
2107
+            promise = track.applyConstraints(localVideoTrack._constraints);
2108
+        }
2109
+
2110
+        return promise
2111
+            .then(() => {
2112
+                // Determine the encodings that need to stay enabled based on the
2113
+                // new frameHeight provided.
2114
+                const encodingsEnabledState = this.tpcUtils.simulcastStreamConstraints
2115
+                    .map(constraint => constraint.height <= frameHeight);
2116
+                const videoSender = this.findSenderByKind(MediaType.VIDEO);
2117
+
2118
+                if (!videoSender) {
2119
+                    return Promise.reject(new Error('RTCRtpSender not found for local video'));
2120
+                }
2121
+                const parameters = videoSender.getParameters();
2122
+
2123
+                if (!parameters || !parameters.encodings || !parameters.encodings.length) {
2124
+                    return Promise.reject(new Error('RTCRtpSendParameters not found for local video track'));
2125
+                }
2126
+                logger.debug(`Setting max height of ${frameHeight} on local video`);
2127
+                for (const encoding in parameters.encodings) {
2128
+                    if (parameters.encodings.hasOwnProperty(encoding)) {
2129
+                        parameters.encodings[encoding].active = encodingsEnabledState[encoding];
2130
+                    }
2131
+                }
2132
+
2133
+                return videoSender.setParameters(parameters);
2134
+            });
2135
+    }
2136
+
2137
+    // Apply the height constraint on the local camera track
2138
+    const aspectRatio = (track.getSettings().width / track.getSettings().height).toPrecision(4);
2139
+
2140
+    logger.debug(`Setting max height of ${frameHeight} on local video`);
2141
+
2142
+    return track.applyConstraints(
2143
+        {
2144
+            aspectRatio,
2145
+            height: {
2146
+                ideal: frameHeight
2147
+            }
2148
+        });
2149
+};
2150
+
2045 2151
 /**
2046 2152
  * Enables/disables video media transmission on this peer connection. When
2047 2153
  * disabled the SDP video media direction in the local SDP will be adjusted to

+ 10
- 0
modules/xmpp/JingleSessionPC.js Ver arquivo

@@ -1328,6 +1328,16 @@ export default class JingleSessionPC extends JingleSession {
1328 1328
             IQ_TIMEOUT);
1329 1329
     }
1330 1330
 
1331
+    /**
1332
+     * Sets the resolution constraint on the local camera track.
1333
+     * @param {number} maxFrameHeight - The user preferred max frame height.
1334
+     * @returns {Promise} promise that will be resolved when the operation is
1335
+     * successful and rejected otherwise.
1336
+     */
1337
+    setSenderVideoConstraint(maxFrameHeight) {
1338
+        return this.peerconnection.setSenderVideoConstraint(maxFrameHeight);
1339
+    }
1340
+
1331 1341
     /**
1332 1342
      * @inheritDoc
1333 1343
      */

Carregando…
Cancelar
Salvar