|
|
@@ -1,10 +1,16 @@
|
|
1
|
1
|
import { safeJsonParse } from '@jitsi/js-utils/json';
|
|
2
|
2
|
import { getLogger } from '@jitsi/logger';
|
|
|
3
|
+import { EventEmitter } from 'events';
|
|
3
|
4
|
|
|
|
5
|
+import JitsiConference from '../../JitsiConference';
|
|
|
6
|
+import { BridgeVideoType } from '../../service/RTC/BridgeVideoType';
|
|
4
|
7
|
import RTCEvents from '../../service/RTC/RTCEvents';
|
|
|
8
|
+import { SourceName } from '../../service/RTC/SignalingLayer';
|
|
5
|
9
|
import { createBridgeChannelClosedEvent } from '../../service/statistics/AnalyticsEvents';
|
|
|
10
|
+import ReceiverVideoConstraints from '../qualitycontrol/ReceiveVideoController';
|
|
6
|
11
|
import Statistics from '../statistics/statistics';
|
|
7
|
12
|
|
|
|
13
|
+
|
|
8
|
14
|
const logger = getLogger(__filename);
|
|
9
|
15
|
|
|
10
|
16
|
/**
|
|
|
@@ -12,6 +18,17 @@ const logger = getLogger(__filename);
|
|
12
|
18
|
* with the videobridge.
|
|
13
|
19
|
*/
|
|
14
|
20
|
export default class BridgeChannel {
|
|
|
21
|
+
|
|
|
22
|
+ private _channel: RTCDataChannel | WebSocket | null = null;
|
|
|
23
|
+ private _conference: JitsiConference;
|
|
|
24
|
+ private _connected: boolean | undefined = undefined;
|
|
|
25
|
+ private _eventEmitter: EventEmitter;
|
|
|
26
|
+ private _mode: 'datachannel' | 'websocket' | null = null;
|
|
|
27
|
+ private _areRetriesEnabled: boolean = false;
|
|
|
28
|
+ private _closedFromClient: boolean = false;
|
|
|
29
|
+ private _wsUrl?: string;
|
|
|
30
|
+ private _retryTimeout?: ReturnType<typeof setTimeout>;
|
|
|
31
|
+
|
|
15
|
32
|
/**
|
|
16
|
33
|
* Binds "ondatachannel" event listener on the given RTCPeerConnection
|
|
17
|
34
|
* instance, or creates a WebSocket connection with the videobridge.
|
|
|
@@ -23,7 +40,12 @@ export default class BridgeChannel {
|
|
23
|
40
|
* @param {EventEmitter} emitter the EventEmitter instance to use for event emission.
|
|
24
|
41
|
* @param {JitsiConference} conference the conference instance.
|
|
25
|
42
|
*/
|
|
26
|
|
- constructor(peerconnection, wsUrl, emitter, conference) {
|
|
|
43
|
+ constructor(
|
|
|
44
|
+ peerconnection: RTCPeerConnection | null,
|
|
|
45
|
+ wsUrl: string | null,
|
|
|
46
|
+ emitter: EventEmitter,
|
|
|
47
|
+ conference: JitsiConference
|
|
|
48
|
+ ) {
|
|
27
|
49
|
if (!peerconnection && !wsUrl) {
|
|
28
|
50
|
throw new TypeError('At least peerconnection or wsUrl must be given');
|
|
29
|
51
|
} else if (peerconnection && wsUrl) {
|
|
|
@@ -86,7 +108,7 @@ export default class BridgeChannel {
|
|
86
|
108
|
*
|
|
87
|
109
|
* @returns {void}
|
|
88
|
110
|
*/
|
|
89
|
|
- _initWebSocket() {
|
|
|
111
|
+ _initWebSocket(): void {
|
|
90
|
112
|
// Create a WebSocket instance.
|
|
91
|
113
|
const ws = new WebSocket(this._wsUrl);
|
|
92
|
114
|
|
|
|
@@ -100,10 +122,10 @@ export default class BridgeChannel {
|
|
100
|
122
|
*
|
|
101
|
123
|
* @returns {void}
|
|
102
|
124
|
*/
|
|
103
|
|
- _startConnectionRetries() {
|
|
|
125
|
+ _startConnectionRetries(): void {
|
|
104
|
126
|
let timeoutS = 1;
|
|
105
|
127
|
|
|
106
|
|
- const reload = () => {
|
|
|
128
|
+ const reload = (): void => {
|
|
107
|
129
|
const isConnecting = this._channel && (this._channel.readyState === 'connecting'
|
|
108
|
130
|
|| this._channel.readyState === WebSocket.CONNECTING);
|
|
109
|
131
|
|
|
|
@@ -119,7 +141,7 @@ export default class BridgeChannel {
|
|
119
|
141
|
if (this.isOpen()) {
|
|
120
|
142
|
return;
|
|
121
|
143
|
}
|
|
122
|
|
- this._initWebSocket(this._wsUrl);
|
|
|
144
|
+ this._initWebSocket();
|
|
123
|
145
|
timeoutS = Math.min(timeoutS * 2, 60);
|
|
124
|
146
|
this._retryTimeout = setTimeout(reload, timeoutS * 1000);
|
|
125
|
147
|
};
|
|
|
@@ -132,7 +154,7 @@ export default class BridgeChannel {
|
|
132
|
154
|
*
|
|
133
|
155
|
* @returns {void}
|
|
134
|
156
|
*/
|
|
135
|
|
- _stopConnectionRetries() {
|
|
|
157
|
+ _stopConnectionRetries(): void {
|
|
136
|
158
|
if (this._retryTimeout) {
|
|
137
|
159
|
clearTimeout(this._retryTimeout);
|
|
138
|
160
|
this._retryTimeout = undefined;
|
|
|
@@ -145,13 +167,13 @@ export default class BridgeChannel {
|
|
145
|
167
|
* @param {CloseEvent} closeEvent - The close event that triggered the retries.
|
|
146
|
168
|
* @returns {void}
|
|
147
|
169
|
*/
|
|
148
|
|
- _retryWebSocketConnection(closeEvent) {
|
|
|
170
|
+ _retryWebSocketConnection(closeEvent: CloseEvent): void {
|
|
149
|
171
|
if (!this._areRetriesEnabled) {
|
|
150
|
172
|
return;
|
|
151
|
173
|
}
|
|
152
|
174
|
const { code, reason } = closeEvent;
|
|
153
|
175
|
|
|
154
|
|
- Statistics.sendAnalytics(createBridgeChannelClosedEvent(code, reason));
|
|
|
176
|
+ Statistics.sendAnalytics(createBridgeChannelClosedEvent(code.toString(), reason));
|
|
155
|
177
|
this._areRetriesEnabled = false;
|
|
156
|
178
|
this._eventEmitter.once(RTCEvents.DATA_CHANNEL_OPEN, () => {
|
|
157
|
179
|
this._stopConnectionRetries();
|
|
|
@@ -164,14 +186,14 @@ export default class BridgeChannel {
|
|
164
|
186
|
* The channel mode.
|
|
165
|
187
|
* @return {string} "datachannel" or "websocket" (or null if not yet set).
|
|
166
|
188
|
*/
|
|
167
|
|
- get mode() {
|
|
|
189
|
+ get mode(): string {
|
|
168
|
190
|
return this._mode;
|
|
169
|
191
|
}
|
|
170
|
192
|
|
|
171
|
193
|
/**
|
|
172
|
194
|
* Closes the currently opened channel.
|
|
173
|
195
|
*/
|
|
174
|
|
- close() {
|
|
|
196
|
+ close(): void {
|
|
175
|
197
|
this._closedFromClient = true;
|
|
176
|
198
|
this._stopConnectionRetries();
|
|
177
|
199
|
this._areRetriesEnabled = false;
|
|
|
@@ -189,7 +211,7 @@ export default class BridgeChannel {
|
|
189
|
211
|
* open.
|
|
190
|
212
|
* @return {boolean}
|
|
191
|
213
|
*/
|
|
192
|
|
- isOpen() {
|
|
|
214
|
+ isOpen(): boolean {
|
|
193
|
215
|
return this._channel && (this._channel.readyState === 'open'
|
|
194
|
216
|
|| this._channel.readyState === WebSocket.OPEN);
|
|
195
|
217
|
}
|
|
|
@@ -199,7 +221,7 @@ export default class BridgeChannel {
|
|
199
|
221
|
* @param {Object} payload The payload of the message.
|
|
200
|
222
|
* @throws NetworkError/InvalidStateError/Error if the operation fails or if there is no data channel created.
|
|
201
|
223
|
*/
|
|
202
|
|
- sendEndpointStatsMessage(payload) {
|
|
|
224
|
+ sendEndpointStatsMessage(payload: Record<string, unknown>): void {
|
|
203
|
225
|
this._send({
|
|
204
|
226
|
colibriClass: 'EndpointStats',
|
|
205
|
227
|
...payload
|
|
|
@@ -215,7 +237,7 @@ export default class BridgeChannel {
|
|
215
|
237
|
* {@link https://developer.mozilla.org/docs/Web/API/RTCDataChannel/send})
|
|
216
|
238
|
* or from WebSocket#send or Error with "No opened channel" message.
|
|
217
|
239
|
*/
|
|
218
|
|
- sendMessage(to, payload) {
|
|
|
240
|
+ sendMessage(to: string, payload: Record<string, unknown>): void {
|
|
219
|
241
|
this._send({
|
|
220
|
242
|
colibriClass: 'EndpointMessage',
|
|
221
|
243
|
msgPayload: payload,
|
|
|
@@ -227,7 +249,7 @@ export default class BridgeChannel {
|
|
227
|
249
|
* Sends a "lastN value changed" message via the channel.
|
|
228
|
250
|
* @param {number} value The new value for lastN. -1 means unlimited.
|
|
229
|
251
|
*/
|
|
230
|
|
- sendSetLastNMessage(value) {
|
|
|
252
|
+ sendSetLastNMessage(value: number): void {
|
|
231
|
253
|
logger.log(`Sending lastN=${value}.`);
|
|
232
|
254
|
|
|
233
|
255
|
this._send({
|
|
|
@@ -241,7 +263,7 @@ export default class BridgeChannel {
|
|
241
|
263
|
*
|
|
242
|
264
|
* @param {ReceiverVideoConstraints} constraints video constraints.
|
|
243
|
265
|
*/
|
|
244
|
|
- sendReceiverVideoConstraintsMessage(constraints) {
|
|
|
266
|
+ sendReceiverVideoConstraintsMessage(constraints: ReceiverVideoConstraints): void {
|
|
245
|
267
|
logger.log(`Sending ReceiverVideoConstraints with ${JSON.stringify(constraints)}`);
|
|
246
|
268
|
this._send({
|
|
247
|
269
|
colibriClass: 'ReceiverVideoConstraints',
|
|
|
@@ -256,7 +278,7 @@ export default class BridgeChannel {
|
|
256
|
278
|
* @param {SourceName} sourceName - the source name of the video track.
|
|
257
|
279
|
* @returns {void}
|
|
258
|
280
|
*/
|
|
259
|
|
- sendSourceVideoTypeMessage(sourceName, videoType) {
|
|
|
281
|
+ sendSourceVideoTypeMessage(sourceName: SourceName, videoType: BridgeVideoType): void {
|
|
260
|
282
|
logger.info(`Sending SourceVideoTypeMessage with video type ${sourceName}: ${videoType}`);
|
|
261
|
283
|
this._send({
|
|
262
|
284
|
colibriClass: 'SourceVideoTypeMessage',
|
|
|
@@ -268,10 +290,10 @@ export default class BridgeChannel {
|
|
268
|
290
|
/**
|
|
269
|
291
|
* Set events on the given RTCDataChannel or WebSocket instance.
|
|
270
|
292
|
*/
|
|
271
|
|
- _handleChannel(channel) {
|
|
|
293
|
+ _handleChannel(channel: RTCDataChannel | WebSocket): void {
|
|
272
|
294
|
const emitter = this._eventEmitter;
|
|
273
|
295
|
|
|
274
|
|
- channel.onopen = () => {
|
|
|
296
|
+ channel.onopen = (): void | null => {
|
|
275
|
297
|
logger.info(`${this._mode} channel opened`);
|
|
276
|
298
|
|
|
277
|
299
|
this._connected = true;
|
|
|
@@ -279,7 +301,7 @@ export default class BridgeChannel {
|
|
279
|
301
|
emitter.emit(RTCEvents.DATA_CHANNEL_OPEN);
|
|
280
|
302
|
};
|
|
281
|
303
|
|
|
282
|
|
- channel.onerror = event => {
|
|
|
304
|
+ channel.onerror = (event: ErrorEvent): void => {
|
|
283
|
305
|
// WS error events contain no information about the failure (this is available in the onclose event) and
|
|
284
|
306
|
// the event references the WS object itself, which causes hangs on mobile.
|
|
285
|
307
|
if (this._mode !== 'websocket') {
|
|
|
@@ -287,7 +309,7 @@ export default class BridgeChannel {
|
|
287
|
309
|
}
|
|
288
|
310
|
};
|
|
289
|
311
|
|
|
290
|
|
- channel.onmessage = ({ data }) => {
|
|
|
312
|
+ channel.onmessage = ({ data }: { data: string; }): void => {
|
|
291
|
313
|
// JSON object.
|
|
292
|
314
|
let obj;
|
|
293
|
315
|
|
|
|
@@ -371,7 +393,7 @@ export default class BridgeChannel {
|
|
371
|
393
|
}
|
|
372
|
394
|
};
|
|
373
|
395
|
|
|
374
|
|
- channel.onclose = event => {
|
|
|
396
|
+ channel.onclose = (event: CloseEvent): void => {
|
|
375
|
397
|
logger.debug(`Channel closed by ${this._closedFromClient ? 'client' : 'server'}`);
|
|
376
|
398
|
|
|
377
|
399
|
if (channel !== this._channel) {
|
|
|
@@ -422,7 +444,7 @@ export default class BridgeChannel {
|
|
422
|
444
|
* {@link https://developer.mozilla.org/docs/Web/API/RTCDataChannel/send})
|
|
423
|
445
|
* or from WebSocket#send or Error with "No opened channel" message.
|
|
424
|
446
|
*/
|
|
425
|
|
- _send(jsonObject) {
|
|
|
447
|
+ _send(jsonObject: Record<string, unknown>): void {
|
|
426
|
448
|
const channel = this._channel;
|
|
427
|
449
|
|
|
428
|
450
|
if (!this.isOpen()) {
|