Parcourir la source

feat(DataChannel): add support for WebSockets in addition to data channels

Some browsers may not support data channels (hi there, Edge!) and sometimes it
may not be desirable for other reasons.
master
Iñaki Baz Castillo il y a 8 ans
Parent
révision
28b541d743

+ 40
- 4
JitsiConference.js Voir le fichier

60
  * "Math.random() < forceJVB121Ratio" will determine whether a 2 people
60
  * "Math.random() < forceJVB121Ratio" will determine whether a 2 people
61
  * conference should be moved to the JVB instead of P2P. The decision is made on
61
  * conference should be moved to the JVB instead of P2P. The decision is made on
62
  * the responder side, after ICE succeeds on the P2P connection.
62
  * the responder side, after ICE succeeds on the P2P connection.
63
+ * @param {*} [options.config.openBridgeChannel] Which kind of communication to
64
+ * open with the videobridge. Values can be "datachannel", "websocket", true
65
+ * (treat it as "datachannel"), undefined (treat it as "datachannel") and false
66
+ * (don't open any channel).
63
  * @constructor
67
  * @constructor
64
  *
68
  *
65
  * FIXME Make all methods which are called from lib-internal classes
69
  * FIXME Make all methods which are called from lib-internal classes
313
 
317
 
314
     this.getLocalTracks().forEach(track => this.onLocalTrackRemoved(track));
318
     this.getLocalTracks().forEach(track => this.onLocalTrackRemoved(track));
315
 
319
 
316
-    this.rtc.closeAllDataChannels();
320
+    this.rtc.closeBridgeChannel();
317
     if (this.statistics) {
321
     if (this.statistics) {
318
         this.statistics.dispose();
322
         this.statistics.dispose();
319
     }
323
     }
1349
         GlobalOnErrorHandler.callErrorHandler(error);
1353
         GlobalOnErrorHandler.callErrorHandler(error);
1350
     }
1354
     }
1351
 
1355
 
1352
-    this.rtc.initializeDataChannels(jingleSession.peerconnection);
1353
-
1354
     // Add local tracks to the session
1356
     // Add local tracks to the session
1355
     try {
1357
     try {
1356
         jingleSession.acceptOffer(
1358
         jingleSession.acceptOffer(
1362
                 if (this.isP2PActive() && this.jvbJingleSession) {
1364
                 if (this.isP2PActive() && this.jvbJingleSession) {
1363
                     this._suspendMediaTransferForJvbConnection();
1365
                     this._suspendMediaTransferForJvbConnection();
1364
                 }
1366
                 }
1367
+
1368
+                // Open a channel with the videobridge.
1369
+                this._setBridgeChannel();
1365
             },
1370
             },
1366
             error => {
1371
             error => {
1367
                 GlobalOnErrorHandler.callErrorHandler(error);
1372
                 GlobalOnErrorHandler.callErrorHandler(error);
1386
     }
1391
     }
1387
 };
1392
 };
1388
 
1393
 
1394
+/**
1395
+ * Sets the BridgeChannel.
1396
+ */
1397
+JitsiConference.prototype._setBridgeChannel = function() {
1398
+    const jingleSession = this.jvbJingleSession;
1399
+    const wsUrl = jingleSession.bridgeWebSocketUrl;
1400
+    let bridgeChannelType;
1401
+
1402
+    switch (this.options.config.openBridgeChannel) {
1403
+    case 'datachannel':
1404
+    case true:
1405
+    case undefined:
1406
+        bridgeChannelType = 'datachannel';
1407
+        break;
1408
+    case 'websocket':
1409
+        bridgeChannelType = 'websocket';
1410
+        break;
1411
+    }
1412
+
1413
+    if (bridgeChannelType === 'datachannel'
1414
+        && !RTCBrowserType.supportsDataChannels()) {
1415
+        bridgeChannelType = 'websocket';
1416
+    }
1417
+
1418
+    if (bridgeChannelType === 'datachannel') {
1419
+        this.rtc.initializeBridgeChannel(jingleSession.peerconnection, null);
1420
+    } else if (bridgeChannelType === 'websocket' && wsUrl) {
1421
+        this.rtc.initializeBridgeChannel(null, wsUrl);
1422
+    }
1423
+};
1424
+
1389
 /**
1425
 /**
1390
  * Rejects incoming Jingle call with 'security-error'. Method should be used to
1426
  * Rejects incoming Jingle call with 'security-error'. Method should be used to
1391
  * reject calls initiated by unauthorised entities.
1427
  * reject calls initiated by unauthorised entities.
1879
  * @throws NetworkError or InvalidStateError or Error if the operation fails.
1915
  * @throws NetworkError or InvalidStateError or Error if the operation fails.
1880
  */
1916
  */
1881
 JitsiConference.prototype.sendEndpointMessage = function(to, payload) {
1917
 JitsiConference.prototype.sendEndpointMessage = function(to, payload) {
1882
-    this.rtc.sendDataChannelMessage(to, payload);
1918
+    this.rtc.sendChannelMessage(to, payload);
1883
 };
1919
 };
1884
 
1920
 
1885
 /**
1921
 /**

+ 6
- 4
JitsiConferenceEventManager.js Voir le fichier

59
 
59
 
60
     chatRoom.addListener(XMPPEvents.ICE_RESTARTING, jingleSession => {
60
     chatRoom.addListener(XMPPEvents.ICE_RESTARTING, jingleSession => {
61
         if (!jingleSession.isP2P) {
61
         if (!jingleSession.isP2P) {
62
-            // All data channels have to be closed, before ICE restart
63
-            // otherwise Chrome will not trigger "opened" event for the channel
64
-            // established with the new bridge
65
-            conference.rtc.closeAllDataChannels();
62
+            // If using DataChannel as bridge channel, it must be closed
63
+            // before ICE restart, otherwise Chrome will not trigger "opened"
64
+            // event for the channel established with the new bridge.
65
+            // TODO: This may be bypassed when using a WebSocket as bridge
66
+            // channel.
67
+            conference.rtc.closeBridgeChannel();
66
         }
68
         }
67
 
69
 
68
         // else: there are no DataChannels in P2P session (at least for now)
70
         // else: there are no DataChannels in P2P session (at least for now)

+ 1
- 1
doc/API.md Voir le fichier

217
 4. initJitsiConference(name, options) - creates new ```JitsiConference``` object.
217
 4. initJitsiConference(name, options) - creates new ```JitsiConference``` object.
218
     - name - the name of the conference
218
     - name - the name of the conference
219
     - options - JS object with configuration options for the conference. You can change the following properties there:
219
     - options - JS object with configuration options for the conference. You can change the following properties there:
220
-        1. openSctp - boolean property. Enables/disables datachannel support. **NOTE: we recommend to set that option to true**
220
+        1. openBridgeChannel - Enables/disables bridge channel. Values can be "datachannel", "websocket", true (treat it as "datachannel"), undefined (treat it as "datachannel") and false (don't open any channel). **NOTE: we recommend to set that option to true**
221
         2. recordingType - the type of recording to be used
221
         2. recordingType - the type of recording to be used
222
         3. jirecon
222
         3. jirecon
223
         4. callStatsID - callstats credentials
223
         4. callStatsID - callstats credentials

+ 1
- 1
doc/example/example.js Voir le fichier

12
 };
12
 };
13
 
13
 
14
 const confOptions = {
14
 const confOptions = {
15
-    openSctp: true
15
+    openBridgeChannel: true
16
 };
16
 };
17
 
17
 
18
 let connection = null;
18
 let connection = null;

+ 293
- 0
modules/RTC/BridgeChannel.js Voir le fichier

1
+import { getLogger } from 'jitsi-meet-logger';
2
+
3
+import RTCEvents from '../../service/RTC/RTCEvents';
4
+import GlobalOnErrorHandler from '../util/GlobalOnErrorHandler';
5
+
6
+const logger = getLogger(__filename);
7
+
8
+/**
9
+ * Handles a WebRTC RTCPeerConnection or a WebSocket instance to communicate
10
+ * with the videobridge.
11
+ */
12
+export default class BridgeChannel {
13
+    /**
14
+     * Binds "ondatachannel" event listener on the given RTCPeerConnection
15
+     * instance, or creates a WebSocket connection with the videobridge.
16
+     * At least one of both, peerconnection or wsUrl parameters, must be
17
+     * given.
18
+     * @param {RTCPeerConnection} [peerconnection] WebRTC peer connection
19
+     * instance.
20
+     * @param {string} [wsUrl] WebSocket URL.
21
+     * @param {EventEmitter} eventEmitter EventEmitter instance.
22
+     */
23
+    constructor(peerconnection, wsUrl, emitter) {
24
+        if (!peerconnection && !wsUrl) {
25
+            throw new TypeError(
26
+                'At least peerconnection or wsUrl must be given');
27
+        } else if (peerconnection && wsUrl) {
28
+            throw new TypeError(
29
+                'Just one of peerconnection or wsUrl must be given');
30
+        }
31
+
32
+        if (peerconnection) {
33
+            logger.debug('constructor() with peerconnection');
34
+        } else {
35
+            logger.debug(`constructor() with wsUrl:"${wsUrl}"`);
36
+        }
37
+
38
+        // The underlying WebRTC RTCDataChannel or WebSocket instance.
39
+        // @type {RTCDataChannel|WebSocket}
40
+        this._channel = null;
41
+
42
+        // @type {EventEmitter}
43
+        this._eventEmitter = emitter;
44
+
45
+        // Whether a RTCDataChannel or WebSocket is internally used.
46
+        // @type {string} "datachannel" / "websocket"
47
+        this._mode = null;
48
+
49
+        // If a RTCPeerConnection is given, listen for new RTCDataChannel
50
+        // event.
51
+        if (peerconnection) {
52
+            peerconnection.ondatachannel = event => {
53
+                // NOTE: We assume that the "onDataChannel" event just fires
54
+                // once.
55
+
56
+                const datachannel = event.channel;
57
+
58
+                // Handle the RTCDataChannel.
59
+                this._handleChannel(datachannel);
60
+                this._mode = 'datachannel';
61
+            };
62
+
63
+        // Otherwise create a WebSocket connection.
64
+        } else if (wsUrl) {
65
+            // Create a WebSocket instance.
66
+            const ws = new WebSocket(wsUrl);
67
+
68
+            // Handle the WebSocket.
69
+            this._handleChannel(ws);
70
+            this._mode = 'websocket';
71
+        }
72
+    }
73
+
74
+    /**
75
+     * The channel mode.
76
+     * @return {string} "datachannel" or "websocket" (or null if not yet set).
77
+     */
78
+    get mode() {
79
+        return this._mode;
80
+    }
81
+
82
+    /**
83
+     * Closes the currently opened channel.
84
+     */
85
+    close() {
86
+        if (this._channel) {
87
+            try {
88
+                this._channel.close();
89
+            } catch (error) {} // eslint-disable-line no-empty
90
+
91
+            this._channel = null;
92
+        }
93
+    }
94
+
95
+    /**
96
+     * Whether there is an underlying RTCDataChannel or WebSocket and it's
97
+     * open.
98
+     * @return {boolean}
99
+     */
100
+    isOpen() {
101
+        return this._channel && (this._channel.readyState === 'open'
102
+            || this._channel.readyState === WebSocket.OPEN);
103
+    }
104
+
105
+    /**
106
+     * Sends message via the channel.
107
+     * @param {string} to The id of the endpoint that should receive the
108
+     * message. If "" the message will be sent to all participants.
109
+     * @param  {object} payload The payload of the message.
110
+     * @throws NetworkError or InvalidStateError from RTCDataChannel#send (@see
111
+     * {@link https://developer.mozilla.org/docs/Web/API/RTCDataChannel/send})
112
+     * or from WebSocket#send or Error with "No opened channel" message.
113
+     */
114
+    sendMessage(to, payload) {
115
+        this._send({
116
+            colibriClass: 'EndpointMessage',
117
+            msgPayload: payload,
118
+            to
119
+        });
120
+    }
121
+
122
+    /**
123
+     * Sends a "lastN value changed" message via the channel.
124
+     * @param {number} value The new value for lastN. -1 means unlimited.
125
+     */
126
+    sendSetLastNMessage(value) {
127
+        const jsonObject = {
128
+            colibriClass: 'LastNChangedEvent',
129
+            lastN: value
130
+        };
131
+
132
+        this._send(jsonObject);
133
+        logger.log(`Channel lastN set to: ${value}`);
134
+    }
135
+
136
+    /**
137
+     * Sends a "pinned endpoint changed" message via the channel.
138
+     * @param {string} endpointId The id of the pinned endpoint.
139
+     * @throws NetworkError or InvalidStateError from RTCDataChannel#send (@see
140
+     * {@link https://developer.mozilla.org/docs/Web/API/RTCDataChannel/send})
141
+     * or from WebSocket#send or Error with "No opened channel" message.
142
+     */
143
+    sendPinnedEndpointMessage(endpointId) {
144
+        logger.log(
145
+            'sending pinned changed notification to the bridge for endpoint ',
146
+            endpointId);
147
+
148
+        this._send({
149
+            colibriClass: 'PinnedEndpointChangedEvent',
150
+            pinnedEndpoint: endpointId || null
151
+        });
152
+    }
153
+
154
+    /**
155
+     * Sends a "selected endpoint changed" message via the channel.
156
+     * @param {string} endpointId The id of the selected endpoint.
157
+     * @throws NetworkError or InvalidStateError from RTCDataChannel#send (@see
158
+     * {@link https://developer.mozilla.org/docs/Web/API/RTCDataChannel/send})
159
+     * or from WebSocket#send or Error with "No opened channel" message.
160
+     */
161
+    sendSelectedEndpointMessage(endpointId) {
162
+        logger.log(
163
+            'sending selected changed notification to the bridge for endpoint ',
164
+            endpointId);
165
+
166
+        this._send({
167
+            colibriClass: 'SelectedEndpointChangedEvent',
168
+            selectedEndpoint: endpointId || null
169
+        });
170
+    }
171
+
172
+    /**
173
+     * Set events on the given RTCDataChannel or WebSocket instance.
174
+     */
175
+    _handleChannel(channel) {
176
+        const emitter = this._eventEmitter;
177
+
178
+        channel.onopen = () => {
179
+            logger.info('Channel opened by the Videobridge!', channel);
180
+
181
+            // Code sample for sending string and/or binary data.
182
+            // Sends string message to the bridge:
183
+            //     channel.send("Hello bridge!");
184
+            // Sends 12 bytes binary message to the bridge:
185
+            //     channel.send(new ArrayBuffer(12));
186
+
187
+            emitter.emit(RTCEvents.DATA_CHANNEL_OPEN);
188
+        };
189
+
190
+        channel.onerror = error => {
191
+            logger.error('Channel error:', error, channel);
192
+        };
193
+
194
+        channel.onmessage = ({ data }) => {
195
+            // JSON object.
196
+            let obj;
197
+
198
+            try {
199
+                obj = JSON.parse(data);
200
+            } catch (error) {
201
+                GlobalOnErrorHandler.callErrorHandler(error);
202
+                logger.error(
203
+                    'Failed to parse channel message as JSON: ',
204
+                    data, channel, error);
205
+
206
+                return;
207
+            }
208
+
209
+            const colibriClass = obj.colibriClass;
210
+
211
+            switch (colibriClass) {
212
+            case 'DominantSpeakerEndpointChangeEvent': {
213
+                // Endpoint ID from the Videobridge.
214
+                const dominantSpeakerEndpoint = obj.dominantSpeakerEndpoint;
215
+
216
+                logger.info(
217
+                    'Channel new dominant speaker event: ',
218
+                    dominantSpeakerEndpoint);
219
+                emitter.emit(
220
+                    RTCEvents.DOMINANT_SPEAKER_CHANGED,
221
+                    dominantSpeakerEndpoint);
222
+                break;
223
+            }
224
+            case 'EndpointConnectivityStatusChangeEvent': {
225
+                const endpoint = obj.endpoint;
226
+                const isActive = obj.active === 'true';
227
+
228
+                logger.info(
229
+                    `Endpoint connection status changed: ${endpoint} active ? ${
230
+                        isActive}`);
231
+                emitter.emit(RTCEvents.ENDPOINT_CONN_STATUS_CHANGED,
232
+                    endpoint, isActive);
233
+
234
+                break;
235
+            }
236
+            case 'EndpointMessage': {
237
+                emitter.emit(
238
+                    RTCEvents.ENDPOINT_MESSAGE_RECEIVED, obj.from,
239
+                    obj.msgPayload);
240
+
241
+                break;
242
+            }
243
+            case 'LastNEndpointsChangeEvent': {
244
+                // The new/latest list of last-n endpoint IDs.
245
+                const lastNEndpoints = obj.lastNEndpoints;
246
+
247
+                logger.info('Channel new last-n event: ',
248
+                    lastNEndpoints, obj);
249
+                emitter.emit(RTCEvents.LASTN_ENDPOINT_CHANGED,
250
+                    lastNEndpoints, obj);
251
+
252
+                break;
253
+            }
254
+            default: {
255
+                logger.debug('Channel JSON-formatted message: ', obj);
256
+
257
+                // The received message appears to be appropriately formatted
258
+                // (i.e. is a JSON object which assigns a value to the
259
+                // mandatory property colibriClass) so don't just swallow it,
260
+                // expose it to public consumption.
261
+                emitter.emit(`rtc.datachannel.${colibriClass}`, obj);
262
+            }
263
+            }
264
+        };
265
+
266
+        channel.onclose = () => {
267
+            logger.info('Channel closed', channel);
268
+
269
+            // Remove the channel.
270
+            this._channel = null;
271
+        };
272
+
273
+        // Store the channel.
274
+        this._channel = channel;
275
+    }
276
+
277
+    /**
278
+     * Sends passed object via the channel.
279
+     * @param {object} jsonObject The object that will be sent.
280
+     * @throws NetworkError or InvalidStateError from RTCDataChannel#send (@see
281
+     * {@link https://developer.mozilla.org/docs/Web/API/RTCDataChannel/send})
282
+     * or from WebSocket#send or Error with "No opened channel" message.
283
+     */
284
+    _send(jsonObject) {
285
+        const channel = this._channel;
286
+
287
+        if (!this.isOpen()) {
288
+            throw new Error('No opened channel');
289
+        }
290
+
291
+        channel.send(JSON.stringify(jsonObject));
292
+    }
293
+}

+ 0
- 276
modules/RTC/DataChannels.js Voir le fichier

1
-// cache datachannels to avoid garbage collection
2
-// https://code.google.com/p/chromium/issues/detail?id=405545
3
-
4
-const logger = require('jitsi-meet-logger').getLogger(__filename);
5
-const RTCEvents = require('../../service/RTC/RTCEvents');
6
-const GlobalOnErrorHandler = require('../util/GlobalOnErrorHandler');
7
-
8
-/**
9
- * Binds "ondatachannel" event listener to given PeerConnection instance.
10
- * @param peerConnection WebRTC peer connection instance.
11
- */
12
-function DataChannels(peerConnection, emitter) {
13
-    peerConnection.ondatachannel = this.onDataChannel.bind(this);
14
-    this.eventEmitter = emitter;
15
-
16
-    this._dataChannels = [];
17
-
18
-    // Sample code for opening new data channel from Jitsi Meet to the bridge.
19
-    // Although it's not a requirement to open separate channels from both
20
-    // bridge and peer as single channel can be used for sending and receiving
21
-    // data. So either channel opened by the bridge or the one opened here is
22
-    // enough for communication with the bridge.
23
-    /* var dataChannelOptions =
24
-     {
25
-     reliable: true
26
-     };
27
-     var dataChannel
28
-     = peerConnection.createDataChannel("myChannel", dataChannelOptions);
29
-
30
-     // Can be used only when is in open state
31
-     dataChannel.onopen = function ()
32
-     {
33
-     dataChannel.send("My channel !!!");
34
-     };
35
-     dataChannel.onmessage = function (event)
36
-     {
37
-     var msgData = event.data;
38
-     logger.info("Got My Data Channel Message:", msgData, dataChannel);
39
-     };*/
40
-}
41
-
42
-/**
43
- * Callback triggered by PeerConnection when new data channel is opened
44
- * on the bridge.
45
- * @param event the event info object.
46
- */
47
-DataChannels.prototype.onDataChannel = function(event) {
48
-    const dataChannel = event.channel;
49
-    const self = this;
50
-
51
-    dataChannel.onopen = function() {
52
-        logger.info('Data channel opened by the Videobridge!');
53
-
54
-        // Code sample for sending string and/or binary data
55
-        // Sends String message to the bridge
56
-        // dataChannel.send("Hello bridge!");
57
-        // Sends 12 bytes binary message to the bridge
58
-        // dataChannel.send(new ArrayBuffer(12));
59
-
60
-        self.eventEmitter.emit(RTCEvents.DATA_CHANNEL_OPEN);
61
-    };
62
-
63
-    dataChannel.onerror = function(error) {
64
-        // FIXME: this one seems to be generated a bit too often right now
65
-        // so we are temporarily commenting it before we have more clarity
66
-        // on which of the errors we absolutely need to report
67
-        // GlobalOnErrorHandler.callErrorHandler(
68
-        //        new Error("Data Channel Error:" + error));
69
-        logger.error('Data Channel Error:', error, dataChannel);
70
-    };
71
-
72
-    dataChannel.onmessage = function({ data }) {
73
-        // JSON
74
-        let obj;
75
-
76
-        try {
77
-            obj = JSON.parse(data);
78
-        } catch (e) {
79
-            GlobalOnErrorHandler.callErrorHandler(e);
80
-            logger.error(
81
-                'Failed to parse data channel message as JSON: ',
82
-                data,
83
-                dataChannel,
84
-                e);
85
-        }
86
-        if ((typeof obj !== 'undefined') && (obj !== null)) {
87
-            const colibriClass = obj.colibriClass;
88
-
89
-            if (colibriClass === 'DominantSpeakerEndpointChangeEvent') {
90
-                // Endpoint ID from the Videobridge.
91
-                const dominantSpeakerEndpoint = obj.dominantSpeakerEndpoint;
92
-
93
-                logger.info(
94
-                    'Data channel new dominant speaker event: ',
95
-                    dominantSpeakerEndpoint);
96
-                self.eventEmitter.emit(RTCEvents.DOMINANT_SPEAKER_CHANGED,
97
-                  dominantSpeakerEndpoint);
98
-            } else if (colibriClass === 'LastNEndpointsChangeEvent') {
99
-                // The new/latest list of last-n endpoint IDs.
100
-                const lastNEndpoints = obj.lastNEndpoints;
101
-
102
-                logger.info('Data channel new last-n event: ',
103
-                    lastNEndpoints, obj);
104
-                self.eventEmitter.emit(RTCEvents.LASTN_ENDPOINT_CHANGED,
105
-                    lastNEndpoints, obj);
106
-            } else if (colibriClass === 'EndpointMessage') {
107
-                self.eventEmitter.emit(
108
-                    RTCEvents.ENDPOINT_MESSAGE_RECEIVED, obj.from,
109
-                    obj.msgPayload);
110
-            } else if (colibriClass
111
-                    === 'EndpointConnectivityStatusChangeEvent') {
112
-                const endpoint = obj.endpoint;
113
-                const isActive = obj.active === 'true';
114
-
115
-                logger.info(
116
-                    `Endpoint connection status changed: ${endpoint} active ? ${
117
-                        isActive}`);
118
-                self.eventEmitter.emit(RTCEvents.ENDPOINT_CONN_STATUS_CHANGED,
119
-                    endpoint, isActive);
120
-            } else {
121
-                logger.debug('Data channel JSON-formatted message: ', obj);
122
-
123
-                // The received message appears to be appropriately formatted
124
-                // (i.e. is a JSON object which assigns a value to the mandatory
125
-                // property colibriClass) so don't just swallow it, expose it to
126
-                // public consumption.
127
-                self.eventEmitter.emit(`rtc.datachannel.${colibriClass}`, obj);
128
-            }
129
-        }
130
-    };
131
-
132
-    dataChannel.onclose = function() {
133
-        logger.info('The Data Channel closed', dataChannel);
134
-        const idx = self._dataChannels.indexOf(dataChannel);
135
-
136
-        if (idx > -1) {
137
-            self._dataChannels = self._dataChannels.splice(idx, 1);
138
-        }
139
-    };
140
-    this._dataChannels.push(dataChannel);
141
-};
142
-
143
-/**
144
- * Closes all currently opened data channels.
145
- */
146
-DataChannels.prototype.closeAllChannels = function() {
147
-    this._dataChannels.forEach(dc => {
148
-        // the DC will be removed from the array on 'onclose' event
149
-        dc.close();
150
-    });
151
-};
152
-
153
-/**
154
- * Sends a "selected endpoint changed" message via the data channel.
155
- * @param endpointId {string} the id of the selected endpoint
156
- * @throws NetworkError or InvalidStateError from RTCDataChannel#send (@see
157
- * {@link https://developer.mozilla.org/en-US/docs/Web/API/RTCDataChannel/send})
158
- * or Error with "No opened data channels found!" message.
159
- */
160
-DataChannels.prototype.sendSelectedEndpointMessage = function(endpointId) {
161
-    this._onXXXEndpointChanged('selected', endpointId);
162
-};
163
-
164
-/**
165
- * Sends a "pinned endpoint changed" message via the data channel.
166
- * @param endpointId {string} the id of the pinned endpoint
167
- * @throws NetworkError or InvalidStateError from RTCDataChannel#send (@see
168
- * {@link https://developer.mozilla.org/en-US/docs/Web/API/RTCDataChannel/send})
169
- * or Error with "No opened data channels found!" message.
170
- */
171
-DataChannels.prototype.sendPinnedEndpointMessage = function(endpointId) {
172
-    this._onXXXEndpointChanged('pinned', endpointId);
173
-};
174
-
175
-/**
176
- * Notifies Videobridge about a change in the value of a specific
177
- * endpoint-related property such as selected endpoint and pinned endpoint.
178
- *
179
- * @param xxx the name of the endpoint-related property whose value changed
180
- * @param userResource the new value of the endpoint-related property after the
181
- * change
182
- * @throws NetworkError or InvalidStateError from RTCDataChannel#send (@see
183
- * {@link https://developer.mozilla.org/en-US/docs/Web/API/RTCDataChannel/send})
184
- * or Error with "No opened data channels found!" message.
185
- */
186
-DataChannels.prototype._onXXXEndpointChanged = function(xxx, userResource) {
187
-    // Derive the correct words from xxx such as selected and Selected, pinned
188
-    // and Pinned.
189
-    const head = xxx.charAt(0);
190
-    const tail = xxx.substring(1);
191
-    const lower = head.toLowerCase() + tail;
192
-    const upper = head.toUpperCase() + tail;
193
-
194
-    logger.log(
195
-            `sending ${lower} endpoint changed notification to the bridge: `,
196
-            userResource);
197
-
198
-    const jsonObject = {};
199
-
200
-    jsonObject.colibriClass = `${upper}EndpointChangedEvent`;
201
-    jsonObject[`${lower}Endpoint`]
202
-        = userResource ? userResource : null;
203
-
204
-    this.send(jsonObject);
205
-
206
-    // Notify Videobridge about the specified endpoint change.
207
-    logger.log(`${lower} endpoint changed: `, userResource);
208
-};
209
-
210
-DataChannels.prototype._some = function(callback, thisArg) {
211
-    const dataChannels = this._dataChannels;
212
-
213
-    if (dataChannels && dataChannels.length !== 0) {
214
-        if (thisArg) {
215
-            return dataChannels.some(callback, thisArg);
216
-        }
217
-
218
-        return dataChannels.some(callback);
219
-
220
-    }
221
-
222
-    return false;
223
-
224
-};
225
-
226
-/**
227
- * Sends passed object via the first found open datachannel
228
- * @param jsonObject {object} the object that will be sent
229
- * @throws NetworkError or InvalidStateError from RTCDataChannel#send (@see
230
- * {@link https://developer.mozilla.org/en-US/docs/Web/API/RTCDataChannel/send})
231
- * or Error with "No opened data channels found!" message.
232
- */
233
-DataChannels.prototype.send = function(jsonObject) {
234
-    if (!this._some(dataChannel => {
235
-        if (dataChannel.readyState === 'open') {
236
-            dataChannel.send(JSON.stringify(jsonObject));
237
-
238
-            return true;
239
-        }
240
-    })) {
241
-        throw new Error('No opened data channels found!');
242
-    }
243
-};
244
-
245
-/**
246
- * Sends message via the datachannels.
247
- * @param to {string} the id of the endpoint that should receive the message.
248
- * If "" the message will be sent to all participants.
249
- * @param payload {object} the payload of the message.
250
- * @throws NetworkError or InvalidStateError from RTCDataChannel#send (@see
251
- * {@link https://developer.mozilla.org/en-US/docs/Web/API/RTCDataChannel/send})
252
- * or Error with "No opened data channels found!" message.
253
- */
254
-DataChannels.prototype.sendDataChannelMessage = function(to, payload) {
255
-    this.send({
256
-        colibriClass: 'EndpointMessage',
257
-        to,
258
-        msgPayload: payload
259
-    });
260
-};
261
-
262
-/**
263
- * Sends a "lastN value changed" message via the data channel.
264
- * @param value {int} The new value for lastN. -1 means unlimited.
265
- */
266
-DataChannels.prototype.sendSetLastNMessage = function(value) {
267
-    const jsonObject = {
268
-        colibriClass: 'LastNChangedEvent',
269
-        lastN: value
270
-    };
271
-
272
-    this.send(jsonObject);
273
-    logger.log(`Channel lastN set to: ${value}`);
274
-};
275
-
276
-module.exports = DataChannels;

+ 147
- 131
modules/RTC/RTC.js Voir le fichier

2
 
2
 
3
 import { getLogger } from 'jitsi-meet-logger';
3
 import { getLogger } from 'jitsi-meet-logger';
4
 
4
 
5
-import DataChannels from './DataChannels';
5
+import BridgeChannel from './BridgeChannel';
6
 import GlobalOnErrorHandler from '../util/GlobalOnErrorHandler';
6
 import GlobalOnErrorHandler from '../util/GlobalOnErrorHandler';
7
 import * as JitsiConferenceEvents from '../../JitsiConferenceEvents';
7
 import * as JitsiConferenceEvents from '../../JitsiConferenceEvents';
8
 import JitsiLocalTrack from './JitsiLocalTrack';
8
 import JitsiLocalTrack from './JitsiLocalTrack';
81
 
81
 
82
         this.options = options;
82
         this.options = options;
83
 
83
 
84
-        // A flag whether we had received that the data channel had opened
85
-        // we can get this flag out of sync if for some reason data channel got
86
-        // closed from server, a desired behaviour so we can see errors when
87
-        // this happen
88
-        this.dataChannelsOpen = false;
84
+        // BridgeChannel instance.
85
+        // @private
86
+        // @type {BridgeChannel}
87
+        this._channel = null;
88
+
89
+        // A flag whether we had received that the channel had opened we can
90
+        // get this flag out of sync if for some reason channel got closed
91
+        // from server, a desired behaviour so we can see errors when this
92
+        // happen.
93
+        // @private
94
+        // @type {boolean}
95
+        this._channelOpen = false;
89
 
96
 
90
         /**
97
         /**
91
          * The value specified to the last invocation of setLastN before the
98
          * The value specified to the last invocation of setLastN before the
92
-         * data channels completed opening. If non-null, the value will be sent
93
-         * through a data channel (once) as soon as it opens and will then be
99
+         * channel completed opening. If non-null, the value will be sent
100
+         * through a channel (once) as soon as it opens and will then be
94
          * discarded.
101
          * discarded.
95
-         *
96
          * @private
102
          * @private
97
          * @type {number}
103
          * @type {number}
98
          */
104
          */
100
 
106
 
101
         /**
107
         /**
102
          * Defines the last N endpoints list. It can be null or an array once
108
          * Defines the last N endpoints list. It can be null or an array once
103
-         * initialised with a datachannel last N event.
109
+         * initialised with a channel last N event.
104
          * @type {Array<string>|null}
110
          * @type {Array<string>|null}
105
          * @private
111
          * @private
106
          */
112
          */
142
 
148
 
143
     /**
149
     /**
144
      * Creates the local MediaStreams.
150
      * Creates the local MediaStreams.
145
-     * @param {Object} [options] optional parameters
146
-     * @param {Array} options.devices the devices that will be requested
147
-     * @param {string} options.resolution resolution constraints
148
-     * @param {bool} options.dontCreateJitsiTrack if <tt>true</tt> objects with
149
-     * the following structure {stream: the Media Stream, type: "audio" or
150
-     * "video", videoType: "camera" or "desktop"} will be returned trough the
151
-     * Promise, otherwise JitsiTrack objects will be returned.
151
+     * @param {object} [options] Optional parameters.
152
+     * @param {array} options.devices The devices that will be requested.
153
+     * @param {string} options.resolution Resolution constraints.
154
+     * @param {bool} options.dontCreateJitsiTrack If <tt>true</tt> objects with
155
+     *     the following structure {stream: the Media Stream, type: "audio" or
156
+     *     "video", videoType: "camera" or "desktop"} will be returned trough
157
+     *     the Promise, otherwise JitsiTrack objects will be returned.
152
      * @param {string} options.cameraDeviceId
158
      * @param {string} options.cameraDeviceId
153
      * @param {string} options.micDeviceId
159
      * @param {string} options.micDeviceId
154
      * @returns {*} Promise object that will receive the new JitsiTracks
160
      * @returns {*} Promise object that will receive the new JitsiTracks
168
     }
174
     }
169
 
175
 
170
     /**
176
     /**
171
-     * Initializes the data channels of this instance.
172
-     * @param peerconnection the associated PeerConnection.
173
-     */
174
-    initializeDataChannels(peerconnection) {
175
-        if (this.options.config.openSctp) {
176
-            this.dataChannels = new DataChannels(peerconnection,
177
-                this.eventEmitter);
178
-
179
-            this._dataChannelOpenListener = () => {
180
-                // mark that dataChannel is opened
181
-                this.dataChannelsOpen = true;
182
-
183
-                // when the data channel becomes available, tell the bridge
184
-                // about video selections so that it can do adaptive simulcast,
185
-                // we want the notification to trigger even if userJid
186
-                // is undefined, or null.
187
-                try {
188
-                    this.dataChannels.sendPinnedEndpointMessage(
189
-                        this._pinnedEndpoint);
190
-                    this.dataChannels.sendSelectedEndpointMessage(
191
-                        this._selectedEndpoint);
192
-                } catch (error) {
193
-                    GlobalOnErrorHandler.callErrorHandler(error);
194
-                    logger.error(
195
-                        `Cannot send selected(${this._selectedEndpoint})`
196
-                        + `pinned(${this._pinnedEndpoint}) endpoint message.`,
197
-                        error);
198
-                }
199
-
200
-                this.removeListener(RTCEvents.DATA_CHANNEL_OPEN,
201
-                    this._dataChannelOpenListener);
202
-                this._dataChannelOpenListener = null;
203
-
204
-                // If setLastN was invoked before the data channels completed
205
-                // opening, apply the specified value now that the data channels
206
-                // are open. NOTE that -1 is the default value assumed by both
207
-                // RTC module and the JVB.
208
-                if (this._lastN !== -1) {
209
-                    this.dataChannels.sendSetLastNMessage(this._lastN);
210
-                }
211
-            };
212
-            this.addListener(RTCEvents.DATA_CHANNEL_OPEN,
213
-                this._dataChannelOpenListener);
214
-
215
-            // Add Last N change listener.
216
-            this.addListener(RTCEvents.LASTN_ENDPOINT_CHANGED,
217
-                this._lastNChangeListener);
218
-        }
177
+     * Initializes the bridge channel of this instance.
178
+     * At least one of both, peerconnection or wsUrl parameters, must be
179
+     * given.
180
+     * @param {RTCPeerConnection} [peerconnection] WebRTC peer connection
181
+     * instance.
182
+     * @param {string} [wsUrl] WebSocket URL.
183
+     */
184
+    initializeBridgeChannel(peerconnection, wsUrl) {
185
+        this._channel = new BridgeChannel(
186
+            peerconnection, wsUrl, this.eventEmitter);
187
+
188
+        this._channelOpenListener = () => {
189
+            // Mark that channel as opened.
190
+            this._channelOpen = true;
191
+
192
+            // When the channel becomes available, tell the bridge about
193
+            // video selections so that it can do adaptive simulcast,
194
+            // we want the notification to trigger even if userJid
195
+            // is undefined, or null.
196
+            try {
197
+                this._channel.sendPinnedEndpointMessage(
198
+                    this._pinnedEndpoint);
199
+                this._channel.sendSelectedEndpointMessage(
200
+                    this._selectedEndpoint);
201
+            } catch (error) {
202
+                GlobalOnErrorHandler.callErrorHandler(error);
203
+                logger.error(
204
+                    `Cannot send selected(${this._selectedEndpoint})`
205
+                    + `pinned(${this._pinnedEndpoint}) endpoint message.`,
206
+                    error);
207
+            }
208
+
209
+            this.removeListener(RTCEvents.DATA_CHANNEL_OPEN,
210
+                this._channelOpenListener);
211
+            this._channelOpenListener = null;
212
+
213
+            // If setLastN was invoked before the bridge channel completed
214
+            // opening, apply the specified value now that the channel
215
+            // is open. NOTE that -1 is the default value assumed by both
216
+            // RTC module and the JVB.
217
+            if (this._lastN !== -1) {
218
+                this._channel.sendSetLastNMessage(this._lastN);
219
+            }
220
+        };
221
+
222
+        this.addListener(RTCEvents.DATA_CHANNEL_OPEN,
223
+            this._channelOpenListener);
224
+
225
+        // Add Last N change listener.
226
+        this.addListener(RTCEvents.LASTN_ENDPOINT_CHANGED,
227
+            this._lastNChangeListener);
219
     }
228
     }
220
 
229
 
221
     /**
230
     /**
222
      * Receives events when Last N had changed.
231
      * Receives events when Last N had changed.
223
-     * @param {array} lastNEndpoints the new Last N endpoints.
232
+     * @param {array} lastNEndpoints The new Last N endpoints.
224
      * @private
233
      * @private
225
      */
234
      */
226
     _onLastNChanged(lastNEndpoints = []) {
235
     _onLastNChanged(lastNEndpoints = []) {
247
      * PeerConnection has been closed using PeerConnection.close() method.
256
      * PeerConnection has been closed using PeerConnection.close() method.
248
      */
257
      */
249
     onCallEnded() {
258
     onCallEnded() {
250
-        if (this.dataChannels) {
251
-            // DataChannels are not explicitly closed as the PeerConnection
252
-            // is closed on call ended which triggers data channel onclose
253
-            // events. The reference is cleared to disable any logic related
254
-            // to the data channels.
255
-            this.dataChannels = null;
256
-            this.dataChannelsOpen = false;
259
+        if (this._channel) {
260
+            // The BridgeChannel is not explicitly closed as the PeerConnection
261
+            // is closed on call ended which triggers datachannel onclose
262
+            // events. If using a WebSocket, the channel must be closed since
263
+            // it is not managed by the PeerConnection.
264
+            // The reference is cleared to disable any logic related to the
265
+            // channel.
266
+            if (this._channel && this._channel.mode === 'websocket') {
267
+                this._channel.close();
268
+            }
269
+
270
+            this._channel = null;
271
+            this._channelOpen = false;
257
         }
272
         }
258
     }
273
     }
259
 
274
 
261
      * Elects the participant with the given id to be the selected participant
276
      * Elects the participant with the given id to be the selected participant
262
      * in order to always receive video for this participant (even when last n
277
      * in order to always receive video for this participant (even when last n
263
      * is enabled).
278
      * is enabled).
264
-     * If there is no data channel we store it and send it through the channel
265
-     * once it is created.
266
-     * @param id {string} the user id.
279
+     * If there is no channel we store it and send it through the channel once
280
+     * it is created.
281
+     * @param {string} id The user id.
267
      * @throws NetworkError or InvalidStateError or Error if the operation
282
      * @throws NetworkError or InvalidStateError or Error if the operation
268
      * fails.
283
      * fails.
269
      */
284
      */
270
     selectEndpoint(id) {
285
     selectEndpoint(id) {
271
-        // cache the value if channel is missing, till we open it
286
+        // Cache the value if channel is missing, till we open it.
272
         this._selectedEndpoint = id;
287
         this._selectedEndpoint = id;
273
-        if (this.dataChannels && this.dataChannelsOpen) {
274
-            this.dataChannels.sendSelectedEndpointMessage(id);
288
+        if (this._channel && this._channelOpen) {
289
+            this._channel.sendSelectedEndpointMessage(id);
275
         }
290
         }
276
     }
291
     }
277
 
292
 
279
      * Elects the participant with the given id to be the pinned participant in
294
      * Elects the participant with the given id to be the pinned participant in
280
      * order to always receive video for this participant (even when last n is
295
      * order to always receive video for this participant (even when last n is
281
      * enabled).
296
      * enabled).
282
-     * @param id {string} the user id
297
+     * @param {stirng} id The user id.
283
      * @throws NetworkError or InvalidStateError or Error if the operation
298
      * @throws NetworkError or InvalidStateError or Error if the operation
284
      * fails.
299
      * fails.
285
      */
300
      */
286
     pinEndpoint(id) {
301
     pinEndpoint(id) {
287
-        // cache the value if channel is missing, till we open it
302
+        // Cache the value if channel is missing, till we open it.
288
         this._pinnedEndpoint = id;
303
         this._pinnedEndpoint = id;
289
-        if (this.dataChannels && this.dataChannelsOpen) {
290
-            this.dataChannels.sendPinnedEndpointMessage(id);
304
+        if (this._channel && this._channelOpen) {
305
+            this._channel.sendPinnedEndpointMessage(id);
291
         }
306
         }
292
     }
307
     }
293
 
308
 
337
 
352
 
338
     /**
353
     /**
339
      * Creates new <tt>TraceablePeerConnection</tt>
354
      * Creates new <tt>TraceablePeerConnection</tt>
340
-     * @param {SignalingLayer} signaling the signaling layer that will
341
-     * provide information about the media or participants which is not carried
342
-     * over SDP.
343
-     * @param {Object} iceConfig an object describing the ICE config like
344
-     * defined in the WebRTC specification.
345
-     * @param {boolean} isP2P indicates whether or not the new TPC will be used
346
-     * in a peer to peer type of session
347
-     * @param {Object} options the config options
348
-     * @param {boolean} options.disableSimulcast if set to 'true' will disable
349
-     * the simulcast
350
-     * @param {boolean} options.disableRtx if set to 'true' will disable the RTX
351
-     * @param {boolean} options.preferH264 if set to 'true' H264 will be
352
-     * preferred over other video codecs.
355
+     * @param {SignalingLayer} signaling The signaling layer that will
356
+     *      provide information about the media or participants which is not
357
+     *      carried over SDP.
358
+     * @param {object} iceConfig An object describing the ICE config like
359
+     *      defined in the WebRTC specification.
360
+     * @param {boolean} isP2P Indicates whether or not the new TPC will be used
361
+     *      in a peer to peer type of session.
362
+     * @param {object} options The config options.
363
+     * @param {boolean} options.disableSimulcast If set to 'true' will disable
364
+     *      the simulcast.
365
+     * @param {boolean} options.disableRtx If set to 'true' will disable the
366
+     *      RTX.
367
+     * @param {boolean} options.preferH264 If set to 'true' H264 will be
368
+     *      preferred over other video codecs.
353
      * @return {TraceablePeerConnection}
369
      * @return {TraceablePeerConnection}
354
      */
370
      */
355
     createPeerConnection(signaling, iceConfig, isP2P, options) {
371
     createPeerConnection(signaling, iceConfig, isP2P, options) {
436
     /**
452
     /**
437
      * Returns the local tracks of the given media type, or all local tracks if
453
      * Returns the local tracks of the given media type, or all local tracks if
438
      * no specific type is given.
454
      * no specific type is given.
439
-     * @param {MediaType} [mediaType] optional media type filter
455
+     * @param {MediaType} [mediaType] Optional media type filter.
440
      * (audio or video).
456
      * (audio or video).
441
      */
457
      */
442
     getLocalTracks(mediaType) {
458
     getLocalTracks(mediaType) {
452
 
468
 
453
     /**
469
     /**
454
      * Obtains all remote tracks currently known to this RTC module instance.
470
      * Obtains all remote tracks currently known to this RTC module instance.
455
-     * @param {MediaType} [mediaType] the remote tracks will be filtered
456
-     * by their media type if this argument is specified.
471
+     * @param {MediaType} [mediaType] The remote tracks will be filtered
472
+     *      by their media type if this argument is specified.
457
      * @return {Array<JitsiRemoteTrack>}
473
      * @return {Array<JitsiRemoteTrack>}
458
      */
474
      */
459
     getRemoteTracks(mediaType) {
475
     getRemoteTracks(mediaType) {
472
 
488
 
473
     /**
489
     /**
474
      * Set mute for all local audio streams attached to the conference.
490
      * Set mute for all local audio streams attached to the conference.
475
-     * @param value the mute value
491
+     * @param value The mute value.
476
      * @returns {Promise}
492
      * @returns {Promise}
477
      */
493
      */
478
     setAudioMute(value) {
494
     setAudioMute(value) {
506
      * Removes all JitsiRemoteTracks associated with given MUC nickname
522
      * Removes all JitsiRemoteTracks associated with given MUC nickname
507
      * (resource part of the JID). Returns array of removed tracks.
523
      * (resource part of the JID). Returns array of removed tracks.
508
      *
524
      *
509
-     * @param {string} owner - The resource part of the MUC JID.
525
+     * @param {string} Owner The resource part of the MUC JID.
510
      * @returns {JitsiRemoteTrack[]}
526
      * @returns {JitsiRemoteTrack[]}
511
      */
527
      */
512
     removeRemoteTracks(owner) {
528
     removeRemoteTracks(owner) {
560
     /**
576
     /**
561
      * Returns true if changing the input (camera / microphone) or output
577
      * Returns true if changing the input (camera / microphone) or output
562
      * (audio) device is supported and false if not.
578
      * (audio) device is supported and false if not.
563
-     * @params {string} [deviceType] - type of device to change. Default is
579
+     * @param {string} [deviceType] Type of device to change. Default is
564
      *      undefined or 'input', 'output' - for audio output device change.
580
      *      undefined or 'input', 'output' - for audio output device change.
565
      * @returns {boolean} true if available, false otherwise.
581
      * @returns {boolean} true if available, false otherwise.
566
      */
582
      */
580
     /**
596
     /**
581
      * Returns list of available media devices if its obtained, otherwise an
597
      * Returns list of available media devices if its obtained, otherwise an
582
      * empty array is returned/
598
      * empty array is returned/
583
-     * @returns {Array} list of available media devices.
599
+     * @returns {array} list of available media devices.
584
      */
600
      */
585
     static getCurrentlyAvailableMediaDevices() {
601
     static getCurrentlyAvailableMediaDevices() {
586
         return RTCUtils.getCurrentlyAvailableMediaDevices();
602
         return RTCUtils.getCurrentlyAvailableMediaDevices();
596
 
612
 
597
     /**
613
     /**
598
      * Sets current audio output device.
614
      * Sets current audio output device.
599
-     * @param {string} deviceId - id of 'audiooutput' device from
600
-     *      navigator.mediaDevices.enumerateDevices()
601
-     * @returns {Promise} - resolves when audio output is changed, is rejected
615
+     * @param {string} deviceId Id of 'audiooutput' device from
616
+     *      navigator.mediaDevices.enumerateDevices().
617
+     * @returns {Promise} resolves when audio output is changed, is rejected
602
      *      otherwise
618
      *      otherwise
603
      */
619
      */
604
     static setAudioOutputDevice(deviceId) {
620
     static setAudioOutputDevice(deviceId) {
614
      * "streams/channels/tracks" for receiving remote stream/tracks, as opposed
630
      * "streams/channels/tracks" for receiving remote stream/tracks, as opposed
615
      * to Plan B where there are only 3 channels: audio, video and data.
631
      * to Plan B where there are only 3 channels: audio, video and data.
616
      *
632
      *
617
-     * @param {MediaStream} stream the WebRTC MediaStream instance
633
+     * @param {MediaStream} stream The WebRTC MediaStream instance.
618
      * @returns {boolean}
634
      * @returns {boolean}
619
      */
635
      */
620
     static isUserStream(stream) {
636
     static isUserStream(stream) {
630
      * "streams/channels/tracks" for receiving remote stream/tracks, as opposed
646
      * "streams/channels/tracks" for receiving remote stream/tracks, as opposed
631
      * to Plan B where there are only 3 channels: audio, video and data.
647
      * to Plan B where there are only 3 channels: audio, video and data.
632
      *
648
      *
633
-     * @param {string} streamId the id of WebRTC MediaStream
649
+     * @param {string} streamId The id of WebRTC MediaStream.
634
      * @returns {boolean}
650
      * @returns {boolean}
635
      */
651
      */
636
     static isUserStreamById(streamId) {
652
     static isUserStreamById(streamId) {
640
 
656
 
641
     /**
657
     /**
642
      * Allows to receive list of available cameras/microphones.
658
      * Allows to receive list of available cameras/microphones.
643
-     * @param {function} callback would receive array of devices as an argument
659
+     * @param {function} callback Would receive array of devices as an
660
+     *      argument.
644
      */
661
      */
645
     static enumerateDevices(callback) {
662
     static enumerateDevices(callback) {
646
         RTCUtils.enumerateDevices(callback);
663
         RTCUtils.enumerateDevices(callback);
649
     /**
666
     /**
650
      * A method to handle stopping of the stream.
667
      * A method to handle stopping of the stream.
651
      * One point to handle the differences in various implementations.
668
      * One point to handle the differences in various implementations.
652
-     * @param mediaStream MediaStream object to stop.
669
+     * @param {MediaStream} mediaStream MediaStream object to stop.
653
      */
670
      */
654
     static stopMediaStream(mediaStream) {
671
     static stopMediaStream(mediaStream) {
655
         RTCUtils.stopMediaStream(mediaStream);
672
         RTCUtils.stopMediaStream(mediaStream);
664
     }
681
     }
665
 
682
 
666
     /**
683
     /**
667
-     * Closes all currently opened data channels.
684
+     * Closes the currently opened bridge channel.
668
      */
685
      */
669
-    closeAllDataChannels() {
670
-        if (this.dataChannels) {
671
-            this.dataChannels.closeAllChannels();
672
-            this.dataChannelsOpen = false;
686
+    closeBridgeChannel() {
687
+        if (this._channel) {
688
+            this._channel.close();
689
+            this._channelOpen = false;
673
 
690
 
674
             this.removeListener(RTCEvents.LASTN_ENDPOINT_CHANGED,
691
             this.removeListener(RTCEvents.LASTN_ENDPOINT_CHANGED,
675
                 this._lastNChangeListener);
692
                 this._lastNChangeListener);
704
     /* eslint-enable max-params */
721
     /* eslint-enable max-params */
705
 
722
 
706
     /**
723
     /**
707
-     * Sends message via the datachannels.
708
-     * @param to {string} the id of the endpoint that should receive the
709
-     * message. If "" the message will be sent to all participants.
710
-     * @param payload {object} the payload of the message.
724
+     * Sends message via the bridge channel.
725
+     * @param {string} to The id of the endpoint that should receive the
726
+     *      message. If "" the message will be sent to all participants.
727
+     * @param {object} payload The payload of the message.
711
      * @throws NetworkError or InvalidStateError or Error if the operation
728
      * @throws NetworkError or InvalidStateError or Error if the operation
712
-     * fails or there is no data channel created
729
+     * fails or there is no data channel created.
713
      */
730
      */
714
-    sendDataChannelMessage(to, payload) {
715
-        if (this.dataChannels) {
716
-            this.dataChannels.sendDataChannelMessage(to, payload);
731
+    sendChannelMessage(to, payload) {
732
+        if (this._channel) {
733
+            this._channel.sendMessage(to, payload);
717
         } else {
734
         } else {
718
-            throw new Error('Data channels support is disabled!');
735
+            throw new Error('Channel support is disabled!');
719
         }
736
         }
720
     }
737
     }
721
 
738
 
723
      * Selects a new value for "lastN". The requested amount of videos are going
740
      * Selects a new value for "lastN". The requested amount of videos are going
724
      * to be delivered after the value is in effect. Set to -1 for unlimited or
741
      * to be delivered after the value is in effect. Set to -1 for unlimited or
725
      * all available videos.
742
      * all available videos.
726
-     * @param value {number} the new value for lastN.
743
+     * @param {number} value the new value for lastN.
727
      */
744
      */
728
     setLastN(value) {
745
     setLastN(value) {
729
         if (this._lastN !== value) {
746
         if (this._lastN !== value) {
730
             this._lastN = value;
747
             this._lastN = value;
731
-            if (this.dataChannels && this.dataChannelsOpen) {
732
-                this.dataChannels.sendSetLastNMessage(value);
748
+            if (this._channel && this._channelOpen) {
749
+                this._channel.sendSetLastNMessage(value);
733
             }
750
             }
734
             this.eventEmitter.emit(RTCEvents.LASTN_VALUE_CHANGED, value);
751
             this.eventEmitter.emit(RTCEvents.LASTN_VALUE_CHANGED, value);
735
         }
752
         }
737
 
754
 
738
     /**
755
     /**
739
      * Indicates if the endpoint id is currently included in the last N.
756
      * Indicates if the endpoint id is currently included in the last N.
740
-     *
741
-     * @param {string} id the endpoint id that we check for last N.
757
+     * @param {string} id The endpoint id that we check for last N.
742
      * @returns {boolean} true if the endpoint id is in the last N or if we
758
      * @returns {boolean} true if the endpoint id is in the last N or if we
743
-     * don't have data channel support, otherwise we return false.
759
+     * don't have bridge channel support, otherwise we return false.
744
      */
760
      */
745
     isInLastN(id) {
761
     isInLastN(id) {
746
-        return !this._lastNEndpoints // lastNEndpoints not initialised yet
762
+        return !this._lastNEndpoints // lastNEndpoints not initialised yet.
747
             || this._lastNEndpoints.indexOf(id) > -1;
763
             || this._lastNEndpoints.indexOf(id) > -1;
748
     }
764
     }
749
 }
765
 }

+ 9
- 0
modules/RTC/RTCBrowserType.js Voir le fichier

218
         return !RTCBrowserType.isFirefox();
218
         return !RTCBrowserType.isFirefox();
219
     },
219
     },
220
 
220
 
221
+    /**
222
+     * Checks if the current browser supports WebRTC datachannels.
223
+     * @return {boolean}
224
+     */
225
+    supportsDataChannels() {
226
+        // NOTE: Edge does not yet implement DataChannel.
227
+        return !RTCBrowserType.isEdge();
228
+    },
229
+
221
     /**
230
     /**
222
      * Checks if the current browser reports round trip time statistics for
231
      * Checks if the current browser reports round trip time statistics for
223
      * the ICE candidate pair.
232
      * the ICE candidate pair.

+ 23
- 0
modules/xmpp/JingleSessionPC.js Voir le fichier

150
          */
150
          */
151
         this._gatheringReported = false;
151
         this._gatheringReported = false;
152
 
152
 
153
+        /**
154
+         * WebSocket URL for the bridge channel with the videobridge.
155
+         * @type {string}
156
+         */
157
+        this.bridgeWebSocketUrl = null;
158
+
153
         this.lasticecandidate = false;
159
         this.lasticecandidate = false;
154
         this.closed = false;
160
         this.closed = false;
155
 
161
 
643
         });
649
         });
644
     }
650
     }
645
 
651
 
652
+    /**
653
+     * Reads the "url" parameter in the <web-socket> tag of the jingle offer iq
654
+     * and stores it into this.bridgeWebSocketUrl.
655
+     * @param contets
656
+     */
657
+    readBridgeWebSocketUrl(contents) {
658
+        const webSocket
659
+            = $(contents)
660
+                .find('transport>web-socket')
661
+                .first();
662
+
663
+        if (webSocket.length === 1) {
664
+            this.bridgeWebSocketUrl = webSocket[0].getAttribute('url');
665
+        }
666
+    }
667
+
646
     /**
668
     /**
647
      * Makes the underlying TraceablePeerConnection generate new SSRC for
669
      * Makes the underlying TraceablePeerConnection generate new SSRC for
648
      * the recvonly video stream.
670
      * the recvonly video stream.
1365
 
1387
 
1366
         remoteSdp.fromJingle(offerIq);
1388
         remoteSdp.fromJingle(offerIq);
1367
         this.readSsrcInfo($(offerIq).find('>content'));
1389
         this.readSsrcInfo($(offerIq).find('>content'));
1390
+        this.readBridgeWebSocketUrl($(offerIq).find('>content'));
1368
 
1391
 
1369
         return remoteSdp;
1392
         return remoteSdp;
1370
     }
1393
     }

+ 25
- 6
modules/xmpp/moderator.js Voir le fichier

1
 /* global $, $iq, Promise, Strophe */
1
 /* global $, $iq, Promise, Strophe */
2
 
2
 
3
 const logger = require('jitsi-meet-logger').getLogger(__filename);
3
 const logger = require('jitsi-meet-logger').getLogger(__filename);
4
+
4
 const XMPPEvents = require('../../service/xmpp/XMPPEvents');
5
 const XMPPEvents = require('../../service/xmpp/XMPPEvents');
5
 const AuthenticationEvents
6
 const AuthenticationEvents
6
     = require('../../service/authentication/AuthenticationEvents');
7
     = require('../../service/authentication/AuthenticationEvents');
7
 const GlobalOnErrorHandler = require('../util/GlobalOnErrorHandler');
8
 const GlobalOnErrorHandler = require('../util/GlobalOnErrorHandler');
8
 
9
 
10
+import RTCBrowserType from '../RTC/RTCBrowserType';
9
 import Settings from '../settings/Settings';
11
 import Settings from '../settings/Settings';
10
 
12
 
11
 /**
13
 /**
210
                 value: this.options.conference.minBitrate
212
                 value: this.options.conference.minBitrate
211
             }).up();
213
             }).up();
212
     }
214
     }
213
-    if (this.options.conference.openSctp !== undefined) {
214
-        elem.c(
215
-            'property', {
216
-                name: 'openSctp',
217
-                value: this.options.conference.openSctp
218
-            }).up();
215
+
216
+    let openSctp;
217
+
218
+    switch (this.options.conference.openBridgeChannel) {
219
+    case 'datachannel':
220
+    case true:
221
+    case undefined:
222
+        openSctp = true;
223
+        break;
224
+    case 'websocket':
225
+        openSctp = false;
226
+        break;
227
+    }
228
+
229
+    if (openSctp && !RTCBrowserType.supportsDataChannels()) {
230
+        openSctp = false;
219
     }
231
     }
232
+
233
+    elem.c(
234
+        'property', {
235
+            name: 'openSctp',
236
+            value: openSctp
237
+        }).up();
238
+
220
     if (this.options.conference.startAudioMuted !== undefined) {
239
     if (this.options.conference.startAudioMuted !== undefined) {
221
         elem.c(
240
         elem.c(
222
             'property', {
241
             'property', {

Chargement…
Annuler
Enregistrer