|
|
@@ -1,6 +1,140 @@
|
|
|
1
|
+import { getLogger } from 'jitsi-meet-logger';
|
|
1
|
2
|
|
|
2
|
3
|
import * as JitsiConferenceEvents from '../../JitsiConferenceEvents';
|
|
3
|
4
|
|
|
|
5
|
+const logger = getLogger(__filename);
|
|
|
6
|
+const MAX_HEIGHT_ONSTAGE = 2160;
|
|
|
7
|
+const MAX_HEIGHT_THUMBNAIL = 180;
|
|
|
8
|
+const LASTN_UNLIMITED = -1;
|
|
|
9
|
+
|
|
|
10
|
+/**
|
|
|
11
|
+ * This class translates the legacy signaling format between the client and the bridge (that affects bandwidth
|
|
|
12
|
+ * allocation) to the new format described here https://github.com/jitsi/jitsi-videobridge/blob/master/doc/allocation.md
|
|
|
13
|
+ */
|
|
|
14
|
+export class ReceiverVideoConstraints {
|
|
|
15
|
+ /**
|
|
|
16
|
+ * Creates a new instance.
|
|
|
17
|
+ */
|
|
|
18
|
+ constructor() {
|
|
|
19
|
+ // Default constraints used for endpoints that are not explicitly included in constraints.
|
|
|
20
|
+ // These constraints are used for endpoints that are thumbnails in the stage view.
|
|
|
21
|
+ this._defaultConstraints = { 'maxHeight': MAX_HEIGHT_THUMBNAIL };
|
|
|
22
|
+
|
|
|
23
|
+ // The number of videos requested from the bridge.
|
|
|
24
|
+ this._lastN = LASTN_UNLIMITED;
|
|
|
25
|
+
|
|
|
26
|
+ // The number representing the maximum video height the local client should receive from the bridge.
|
|
|
27
|
+ this._maxFrameHeight = MAX_HEIGHT_ONSTAGE;
|
|
|
28
|
+
|
|
|
29
|
+ // The endpoint IDs of the participants that are currently selected.
|
|
|
30
|
+ this._selectedEndpoints = [];
|
|
|
31
|
+
|
|
|
32
|
+ this._receiverVideoConstraints = {
|
|
|
33
|
+ constraints: {},
|
|
|
34
|
+ defaultConstraints: this.defaultConstraints,
|
|
|
35
|
+ lastN: this._lastN,
|
|
|
36
|
+ onStageEndpoints: [],
|
|
|
37
|
+ selectedEndpoints: this._selectedEndpoints
|
|
|
38
|
+ };
|
|
|
39
|
+ }
|
|
|
40
|
+
|
|
|
41
|
+ /**
|
|
|
42
|
+ * Returns the receiver video constraints that need to be sent on the bridge channel.
|
|
|
43
|
+ */
|
|
|
44
|
+ get constraints() {
|
|
|
45
|
+ this._receiverVideoConstraints.lastN = this._lastN;
|
|
|
46
|
+
|
|
|
47
|
+ if (!this._selectedEndpoints.length) {
|
|
|
48
|
+ return this._receiverVideoConstraints;
|
|
|
49
|
+ }
|
|
|
50
|
+
|
|
|
51
|
+ // The client is assumed to be in TileView if it has selected more than one endpoint, otherwise it is
|
|
|
52
|
+ // assumed to be in StageView.
|
|
|
53
|
+ this._receiverVideoConstraints.constraints = {};
|
|
|
54
|
+ if (this._selectedEndpoints.length > 1) {
|
|
|
55
|
+ /**
|
|
|
56
|
+ * Tile view.
|
|
|
57
|
+ * Only the default constraints are specified here along with lastN (if it is set).
|
|
|
58
|
+ * {
|
|
|
59
|
+ * 'colibriClass': 'ReceiverVideoConstraints',
|
|
|
60
|
+ * 'defaultConstraints': { 'maxHeight': 360 }
|
|
|
61
|
+ * }
|
|
|
62
|
+ */
|
|
|
63
|
+ this._receiverVideoConstraints.defaultConstraints = { 'maxHeight': this._maxFrameHeight };
|
|
|
64
|
+ this._receiverVideoConstraints.onStageEndpoints = [];
|
|
|
65
|
+ this._receiverVideoConstraints.selectedEndpoints = [];
|
|
|
66
|
+ } else {
|
|
|
67
|
+ /**
|
|
|
68
|
+ * Stage view.
|
|
|
69
|
+ * The participant on stage is specified in onStageEndpoints and a higher maxHeight is specified
|
|
|
70
|
+ * for that endpoint while a default maxHeight of 180 is applied to all the other endpoints.
|
|
|
71
|
+ * {
|
|
|
72
|
+ * 'colibriClass': 'ReceiverVideoConstraints',
|
|
|
73
|
+ * 'onStageEndpoints': ['A'],
|
|
|
74
|
+ * 'defaultConstraints': { 'maxHeight': 180 },
|
|
|
75
|
+ * 'constraints': {
|
|
|
76
|
+ * 'A': { 'maxHeight': 720 }
|
|
|
77
|
+ * }
|
|
|
78
|
+ * }
|
|
|
79
|
+ */
|
|
|
80
|
+ this._receiverVideoConstraints.constraints[this._selectedEndpoints[0]] = {
|
|
|
81
|
+ 'maxHeight': this._maxFrameHeight
|
|
|
82
|
+ };
|
|
|
83
|
+ this._receiverVideoConstraints.defaultConstraints = this._defaultConstraints;
|
|
|
84
|
+ this._receiverVideoConstraints.onStageEndpoints = this._selectedEndpoints;
|
|
|
85
|
+ this._receiverVideoConstraints.selectedEndpoints = [];
|
|
|
86
|
+ }
|
|
|
87
|
+
|
|
|
88
|
+ return this._receiverVideoConstraints;
|
|
|
89
|
+ }
|
|
|
90
|
+
|
|
|
91
|
+ /**
|
|
|
92
|
+ * Updates the lastN field of the ReceiverVideoConstraints sent to the bridge.
|
|
|
93
|
+ *
|
|
|
94
|
+ * @param {number} value
|
|
|
95
|
+ * @returns {boolean} Returns true if the the value has been updated, false otherwise.
|
|
|
96
|
+ */
|
|
|
97
|
+ updateLastN(value) {
|
|
|
98
|
+ const changed = this._lastN !== value;
|
|
|
99
|
+
|
|
|
100
|
+ if (changed) {
|
|
|
101
|
+ this._lastN = value;
|
|
|
102
|
+ logger.debug(`Updating ReceiverVideoConstraints lastN(${value})`);
|
|
|
103
|
+ }
|
|
|
104
|
+
|
|
|
105
|
+ return changed;
|
|
|
106
|
+ }
|
|
|
107
|
+
|
|
|
108
|
+ /**
|
|
|
109
|
+ * Updates the resolution (height requested) in the contraints field of the ReceiverVideoConstraints
|
|
|
110
|
+ * sent to the bridge.
|
|
|
111
|
+ *
|
|
|
112
|
+ * @param {number} maxFrameHeight
|
|
|
113
|
+ * @requires {boolean} Returns true if the the value has been updated, false otherwise.
|
|
|
114
|
+ */
|
|
|
115
|
+ updateReceiveResolution(maxFrameHeight) {
|
|
|
116
|
+ const changed = this._maxFrameHeight !== maxFrameHeight;
|
|
|
117
|
+
|
|
|
118
|
+ if (changed) {
|
|
|
119
|
+ this._maxFrameHeight = maxFrameHeight;
|
|
|
120
|
+ logger.debug(`Updating receive maxFrameHeight: ${maxFrameHeight}`);
|
|
|
121
|
+ }
|
|
|
122
|
+
|
|
|
123
|
+ return changed;
|
|
|
124
|
+ }
|
|
|
125
|
+
|
|
|
126
|
+ /**
|
|
|
127
|
+ * Updates the list of selected endpoints.
|
|
|
128
|
+ *
|
|
|
129
|
+ * @param {Array<string>} ids
|
|
|
130
|
+ * @returns {void}
|
|
|
131
|
+ */
|
|
|
132
|
+ updateSelectedEndpoints(ids) {
|
|
|
133
|
+ logger.debug(`Updating selected endpoints: ${JSON.stringify(ids)}`);
|
|
|
134
|
+ this._selectedEndpoints = ids;
|
|
|
135
|
+ }
|
|
|
136
|
+}
|
|
|
137
|
+
|
|
4
|
138
|
/**
|
|
5
|
139
|
* This class manages the receive video contraints for a given {@link JitsiConference}. These constraints are
|
|
6
|
140
|
* determined by the application based on how the remote video streams need to be displayed. This class is responsible
|
|
|
@@ -18,11 +152,16 @@ export class ReceiveVideoController {
|
|
18
|
152
|
this._conference = conference;
|
|
19
|
153
|
this._rtc = rtc;
|
|
20
|
154
|
|
|
|
155
|
+ // Translate the legacy bridge channel signaling format to the new format.
|
|
|
156
|
+ this._receiverVideoConstraints = conference.options?.config?.useNewBandwidthAllocationStrategy
|
|
|
157
|
+ ? new ReceiverVideoConstraints()
|
|
|
158
|
+ : undefined;
|
|
|
159
|
+
|
|
21
|
160
|
// The number of videos requested from the bridge, -1 represents unlimited or all available videos.
|
|
22
|
|
- this._lastN = -1;
|
|
|
161
|
+ this._lastN = LASTN_UNLIMITED;
|
|
23
|
162
|
|
|
24
|
163
|
// The number representing the maximum video height the local client should receive from the bridge.
|
|
25
|
|
- this._maxFrameHeight = 2160;
|
|
|
164
|
+ this._maxFrameHeight = MAX_HEIGHT_ONSTAGE;
|
|
26
|
165
|
|
|
27
|
166
|
// The endpoint IDs of the participants that are currently selected.
|
|
28
|
167
|
this._selectedEndpoints = [];
|
|
|
@@ -53,6 +192,16 @@ export class ReceiveVideoController {
|
|
53
|
192
|
*/
|
|
54
|
193
|
selectEndpoints(ids) {
|
|
55
|
194
|
this._selectedEndpoints = ids;
|
|
|
195
|
+
|
|
|
196
|
+ if (this._receiverVideoConstraints) {
|
|
|
197
|
+ const remoteEndpointIds = ids.filter(id => id !== this._conference.myUserId());
|
|
|
198
|
+
|
|
|
199
|
+ // Filter out the local endpointId from the list of selected endpoints.
|
|
|
200
|
+ remoteEndpointIds.length && this._receiverVideoConstraints.updateSelectedEndpoints(remoteEndpointIds);
|
|
|
201
|
+ this._rtc.setNewReceiverVideoConstraints(this._receiverVideoConstraints.constraints);
|
|
|
202
|
+
|
|
|
203
|
+ return;
|
|
|
204
|
+ }
|
|
56
|
205
|
this._rtc.selectEndpoints(ids);
|
|
57
|
206
|
}
|
|
58
|
207
|
|
|
|
@@ -66,6 +215,15 @@ export class ReceiveVideoController {
|
|
66
|
215
|
setLastN(value) {
|
|
67
|
216
|
if (this._lastN !== value) {
|
|
68
|
217
|
this._lastN = value;
|
|
|
218
|
+
|
|
|
219
|
+ if (this._receiverVideoConstraints) {
|
|
|
220
|
+ const lastNUpdated = this._receiverVideoConstraints.updateLastN(value);
|
|
|
221
|
+
|
|
|
222
|
+ // Send out the message on the bridge channel if lastN was updated.
|
|
|
223
|
+ lastNUpdated && this._rtc.setNewReceiverVideoConstraints(this._receiverVideoConstraints.constraints);
|
|
|
224
|
+
|
|
|
225
|
+ return;
|
|
|
226
|
+ }
|
|
69
|
227
|
this._rtc.setLastN(value);
|
|
70
|
228
|
}
|
|
71
|
229
|
}
|
|
|
@@ -80,7 +238,14 @@ export class ReceiveVideoController {
|
|
80
|
238
|
this._maxFrameHeight = maxFrameHeight;
|
|
81
|
239
|
|
|
82
|
240
|
for (const session of this._conference._getMediaSessions()) {
|
|
83
|
|
- maxFrameHeight && session.setReceiverVideoConstraint(maxFrameHeight);
|
|
|
241
|
+ if (session.isP2P || !this._receiverVideoConstraints) {
|
|
|
242
|
+ maxFrameHeight && session.setReceiverVideoConstraint(maxFrameHeight);
|
|
|
243
|
+ } else {
|
|
|
244
|
+ const resolutionUpdated = this._receiverVideoConstraints.updateReceiveResolution(maxFrameHeight);
|
|
|
245
|
+
|
|
|
246
|
+ resolutionUpdated
|
|
|
247
|
+ && this._rtc.setNewReceiverVideoConstraints(this._receiverVideoConstraints.constraints);
|
|
|
248
|
+ }
|
|
84
|
249
|
}
|
|
85
|
250
|
}
|
|
86
|
251
|
}
|