瀏覽代碼

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 8 年之前
父節點
當前提交
28b541d743

+ 40
- 4
JitsiConference.js 查看文件

@@ -60,6 +60,10 @@ const logger = getLogger(__filename);
60 60
  * "Math.random() < forceJVB121Ratio" will determine whether a 2 people
61 61
  * conference should be moved to the JVB instead of P2P. The decision is made on
62 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 67
  * @constructor
64 68
  *
65 69
  * FIXME Make all methods which are called from lib-internal classes
@@ -313,7 +317,7 @@ JitsiConference.prototype.leave = function() {
313 317
 
314 318
     this.getLocalTracks().forEach(track => this.onLocalTrackRemoved(track));
315 319
 
316
-    this.rtc.closeAllDataChannels();
320
+    this.rtc.closeBridgeChannel();
317 321
     if (this.statistics) {
318 322
         this.statistics.dispose();
319 323
     }
@@ -1349,8 +1353,6 @@ JitsiConference.prototype.onIncomingCall
1349 1353
         GlobalOnErrorHandler.callErrorHandler(error);
1350 1354
     }
1351 1355
 
1352
-    this.rtc.initializeDataChannels(jingleSession.peerconnection);
1353
-
1354 1356
     // Add local tracks to the session
1355 1357
     try {
1356 1358
         jingleSession.acceptOffer(
@@ -1362,6 +1364,9 @@ JitsiConference.prototype.onIncomingCall
1362 1364
                 if (this.isP2PActive() && this.jvbJingleSession) {
1363 1365
                     this._suspendMediaTransferForJvbConnection();
1364 1366
                 }
1367
+
1368
+                // Open a channel with the videobridge.
1369
+                this._setBridgeChannel();
1365 1370
             },
1366 1371
             error => {
1367 1372
                 GlobalOnErrorHandler.callErrorHandler(error);
@@ -1386,6 +1391,37 @@ JitsiConference.prototype.onIncomingCall
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 1426
  * Rejects incoming Jingle call with 'security-error'. Method should be used to
1391 1427
  * reject calls initiated by unauthorised entities.
@@ -1879,7 +1915,7 @@ JitsiConference.prototype._fireIncompatibleVersionsEvent = function() {
1879 1915
  * @throws NetworkError or InvalidStateError or Error if the operation fails.
1880 1916
  */
1881 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 查看文件

@@ -59,10 +59,12 @@ JitsiConferenceEventManager.prototype.setupChatRoomListeners = function() {
59 59
 
60 60
     chatRoom.addListener(XMPPEvents.ICE_RESTARTING, jingleSession => {
61 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 70
         // else: there are no DataChannels in P2P session (at least for now)

+ 1
- 1
doc/API.md 查看文件

@@ -217,7 +217,7 @@ This objects represents the server connection. You can create new ```JitsiConnec
217 217
 4. initJitsiConference(name, options) - creates new ```JitsiConference``` object.
218 218
     - name - the name of the conference
219 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 221
         2. recordingType - the type of recording to be used
222 222
         3. jirecon
223 223
         4. callStatsID - callstats credentials

+ 1
- 1
doc/example/example.js 查看文件

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

+ 293
- 0
modules/RTC/BridgeChannel.js 查看文件

@@ -0,0 +1,293 @@
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 查看文件

@@ -1,276 +0,0 @@
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 查看文件

@@ -2,7 +2,7 @@
2 2
 
3 3
 import { getLogger } from 'jitsi-meet-logger';
4 4
 
5
-import DataChannels from './DataChannels';
5
+import BridgeChannel from './BridgeChannel';
6 6
 import GlobalOnErrorHandler from '../util/GlobalOnErrorHandler';
7 7
 import * as JitsiConferenceEvents from '../../JitsiConferenceEvents';
8 8
 import JitsiLocalTrack from './JitsiLocalTrack';
@@ -81,18 +81,24 @@ export default class RTC extends Listenable {
81 81
 
82 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 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 101
          * discarded.
95
-         *
96 102
          * @private
97 103
          * @type {number}
98 104
          */
@@ -100,7 +106,7 @@ export default class RTC extends Listenable {
100 106
 
101 107
         /**
102 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 110
          * @type {Array<string>|null}
105 111
          * @private
106 112
          */
@@ -142,13 +148,13 @@ export default class RTC extends Listenable {
142 148
 
143 149
     /**
144 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 158
      * @param {string} options.cameraDeviceId
153 159
      * @param {string} options.micDeviceId
154 160
      * @returns {*} Promise object that will receive the new JitsiTracks
@@ -168,59 +174,62 @@ export default class RTC extends Listenable {
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 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 233
      * @private
225 234
      */
226 235
     _onLastNChanged(lastNEndpoints = []) {
@@ -247,13 +256,19 @@ export default class RTC extends Listenable {
247 256
      * PeerConnection has been closed using PeerConnection.close() method.
248 257
      */
249 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,17 +276,17 @@ export default class RTC extends Listenable {
261 276
      * Elects the participant with the given id to be the selected participant
262 277
      * in order to always receive video for this participant (even when last n
263 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 282
      * @throws NetworkError or InvalidStateError or Error if the operation
268 283
      * fails.
269 284
      */
270 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 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,15 +294,15 @@ export default class RTC extends Listenable {
279 294
      * Elects the participant with the given id to be the pinned participant in
280 295
      * order to always receive video for this participant (even when last n is
281 296
      * enabled).
282
-     * @param id {string} the user id
297
+     * @param {stirng} id The user id.
283 298
      * @throws NetworkError or InvalidStateError or Error if the operation
284 299
      * fails.
285 300
      */
286 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 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,19 +352,20 @@ export default class RTC extends Listenable {
337 352
 
338 353
     /**
339 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 369
      * @return {TraceablePeerConnection}
354 370
      */
355 371
     createPeerConnection(signaling, iceConfig, isP2P, options) {
@@ -436,7 +452,7 @@ export default class RTC extends Listenable {
436 452
     /**
437 453
      * Returns the local tracks of the given media type, or all local tracks if
438 454
      * no specific type is given.
439
-     * @param {MediaType} [mediaType] optional media type filter
455
+     * @param {MediaType} [mediaType] Optional media type filter.
440 456
      * (audio or video).
441 457
      */
442 458
     getLocalTracks(mediaType) {
@@ -452,8 +468,8 @@ export default class RTC extends Listenable {
452 468
 
453 469
     /**
454 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 473
      * @return {Array<JitsiRemoteTrack>}
458 474
      */
459 475
     getRemoteTracks(mediaType) {
@@ -472,7 +488,7 @@ export default class RTC extends Listenable {
472 488
 
473 489
     /**
474 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 492
      * @returns {Promise}
477 493
      */
478 494
     setAudioMute(value) {
@@ -506,7 +522,7 @@ export default class RTC extends Listenable {
506 522
      * Removes all JitsiRemoteTracks associated with given MUC nickname
507 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 526
      * @returns {JitsiRemoteTrack[]}
511 527
      */
512 528
     removeRemoteTracks(owner) {
@@ -560,7 +576,7 @@ export default class RTC extends Listenable {
560 576
     /**
561 577
      * Returns true if changing the input (camera / microphone) or output
562 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 580
      *      undefined or 'input', 'output' - for audio output device change.
565 581
      * @returns {boolean} true if available, false otherwise.
566 582
      */
@@ -580,7 +596,7 @@ export default class RTC extends Listenable {
580 596
     /**
581 597
      * Returns list of available media devices if its obtained, otherwise an
582 598
      * empty array is returned/
583
-     * @returns {Array} list of available media devices.
599
+     * @returns {array} list of available media devices.
584 600
      */
585 601
     static getCurrentlyAvailableMediaDevices() {
586 602
         return RTCUtils.getCurrentlyAvailableMediaDevices();
@@ -596,9 +612,9 @@ export default class RTC extends Listenable {
596 612
 
597 613
     /**
598 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 618
      *      otherwise
603 619
      */
604 620
     static setAudioOutputDevice(deviceId) {
@@ -614,7 +630,7 @@ export default class RTC extends Listenable {
614 630
      * "streams/channels/tracks" for receiving remote stream/tracks, as opposed
615 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 634
      * @returns {boolean}
619 635
      */
620 636
     static isUserStream(stream) {
@@ -630,7 +646,7 @@ export default class RTC extends Listenable {
630 646
      * "streams/channels/tracks" for receiving remote stream/tracks, as opposed
631 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 650
      * @returns {boolean}
635 651
      */
636 652
     static isUserStreamById(streamId) {
@@ -640,7 +656,8 @@ export default class RTC extends Listenable {
640 656
 
641 657
     /**
642 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 662
     static enumerateDevices(callback) {
646 663
         RTCUtils.enumerateDevices(callback);
@@ -649,7 +666,7 @@ export default class RTC extends Listenable {
649 666
     /**
650 667
      * A method to handle stopping of the stream.
651 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 671
     static stopMediaStream(mediaStream) {
655 672
         RTCUtils.stopMediaStream(mediaStream);
@@ -664,12 +681,12 @@ export default class RTC extends Listenable {
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 691
             this.removeListener(RTCEvents.LASTN_ENDPOINT_CHANGED,
675 692
                 this._lastNChangeListener);
@@ -704,18 +721,18 @@ export default class RTC extends Listenable {
704 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 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 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,13 +740,13 @@ export default class RTC extends Listenable {
723 740
      * Selects a new value for "lastN". The requested amount of videos are going
724 741
      * to be delivered after the value is in effect. Set to -1 for unlimited or
725 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 745
     setLastN(value) {
729 746
         if (this._lastN !== value) {
730 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 751
             this.eventEmitter.emit(RTCEvents.LASTN_VALUE_CHANGED, value);
735 752
         }
@@ -737,13 +754,12 @@ export default class RTC extends Listenable {
737 754
 
738 755
     /**
739 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 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 761
     isInLastN(id) {
746
-        return !this._lastNEndpoints // lastNEndpoints not initialised yet
762
+        return !this._lastNEndpoints // lastNEndpoints not initialised yet.
747 763
             || this._lastNEndpoints.indexOf(id) > -1;
748 764
     }
749 765
 }

+ 9
- 0
modules/RTC/RTCBrowserType.js 查看文件

@@ -218,6 +218,15 @@ const RTCBrowserType = {
218 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 231
      * Checks if the current browser reports round trip time statistics for
223 232
      * the ICE candidate pair.

+ 23
- 0
modules/xmpp/JingleSessionPC.js 查看文件

@@ -150,6 +150,12 @@ export default class JingleSessionPC extends JingleSession {
150 150
          */
151 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 159
         this.lasticecandidate = false;
154 160
         this.closed = false;
155 161
 
@@ -643,6 +649,22 @@ export default class JingleSessionPC extends JingleSession {
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 669
      * Makes the underlying TraceablePeerConnection generate new SSRC for
648 670
      * the recvonly video stream.
@@ -1365,6 +1387,7 @@ export default class JingleSessionPC extends JingleSession {
1365 1387
 
1366 1388
         remoteSdp.fromJingle(offerIq);
1367 1389
         this.readSsrcInfo($(offerIq).find('>content'));
1390
+        this.readBridgeWebSocketUrl($(offerIq).find('>content'));
1368 1391
 
1369 1392
         return remoteSdp;
1370 1393
     }

+ 25
- 6
modules/xmpp/moderator.js 查看文件

@@ -1,11 +1,13 @@
1 1
 /* global $, $iq, Promise, Strophe */
2 2
 
3 3
 const logger = require('jitsi-meet-logger').getLogger(__filename);
4
+
4 5
 const XMPPEvents = require('../../service/xmpp/XMPPEvents');
5 6
 const AuthenticationEvents
6 7
     = require('../../service/authentication/AuthenticationEvents');
7 8
 const GlobalOnErrorHandler = require('../util/GlobalOnErrorHandler');
8 9
 
10
+import RTCBrowserType from '../RTC/RTCBrowserType';
9 11
 import Settings from '../settings/Settings';
10 12
 
11 13
 /**
@@ -210,13 +212,30 @@ Moderator.prototype.createConferenceIq = function() {
210 212
                 value: this.options.conference.minBitrate
211 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 239
     if (this.options.conference.startAudioMuted !== undefined) {
221 240
         elem.c(
222 241
             'property', {

Loading…
取消
儲存