浏览代码

P2P ver2 (#413)

* ref(RTC): store remote tracks in peer TPC

In order to implement P2P <-> JVB connection switching we need to
be able to associate remote tracks with the TraceablePeerConnections.

* feat(ChatRoom): multiple presence handlers

Add support for more than 1 presence handler per tag name.

* feat(JitsiLocalTrack): update stored MSID

* ref(stats): add peer connection arg to BYTE_SENT_STATS

Required to store local SSRCs in TraceablePeerConnection.

* ref: change local SSRCs strategy

* fix: generate recvonly SSRC if 0 video tracks

Video SSRC has to be generated for the recvonly stream if there are no
video tracks in the PeerConnection.

* feat: add "attach" and "detach" methods

* feat(jitsi tracks): improve logging

Adds toString methods and improve log messages around local and remote
tracks.

* ref(modify SSRCs): optimisations + fixes

- adds _doRenegotiate to JingleSessionPC that wraps some of
  the duplicated logic
- fixes problems with attach/detach
- renames methods to reflect what that they really do (operate on
  JitsiTrack rather than streams)

* ref(JingleSessionPC): remove duplication

Extracts common code for the 'modificationQueue' execution

* ref(VideoMuteSdpHack): rename, add docs, fix minor

Renames, adds docs and moves 'modified' flag and media direction
modification.

* ref(TPC): add 'isSimulcastOn'

* ref(MungeLocalSdp): move to RTC module

* ref: move "ufrag" events to the RTC module

* ref(JitsiConference): use promises for mute

* feat(XMPPEvents): add CONNECTION_ESTABLISHED

* feat: add peer to peer

* fix(P2P): deal with everyone's a moderator + fixes

* feat(P2P): implement "backToP2PDelay"

* fix(TPC): crash on FF accessing LD/RD

Firefox return null/undefined for localDescription/remoteDescription
objects if they have not been set yet, while Chrome does return empty
object instead. This commit makes the behaviour consistent by making
sure that at least empty object is returned for all browsers.

* fix(TPC): replace isFirefox with feature

* fix(JSPC): fix renegotiate crash on FF

FF does not allow to call 'createAnswer' in 'have-local-offer' state

* fix(JSPC): fix addIceCandidate crash on FF

* doc(JitsiConference): fix outdated comment

To be squashed with "ref(ChatRoom): remove unnecessary JingleSessionPC dependency"

* style(JitsiConference): rename arg to "jingleSession"

* feat(stats): add 'p2p' to 'transport'

The new p2p field will inform whether the transport comes from the peer
to peer type of connection or not.

* doc(TPC): describe local maps

* fix(P2P): multiplex between JVB and P2P ICE status

Will make sure that when in P2P mode the conference will be updated
with the ICE state coming from P2P and when in the JVB mode will get
the JVB one.

* doc(TPC): fixes docs and adds FIXME

* ref: use 'doesVideoMuteByStreamRemove'

* feat(P2P): stop P2P when ICE enters FAILED

The conference will switch back to the JVB connection when P2P
connection breaks (ICE enters failed state).

* feat(P2P): "connectivity-error" for ICE failed

Will use "connectivity-error" reason element name when ending P2P
session due to ICE failure.

* feat(xmpp): make P2P Stun servers configurable

STUN servers used in the P2P connection can be configured through
"p2pStunServers" option.

* ref(JitsiConference): use 'getActivePeerConnection'

* fix(P2P): re-create 'dtmfManager'

* ref(P2P): deal with ICE "completed" state

* ref(RTC): rename "owner" to "ownerEndpointId"

* fix(MungeLocalSdp): fix directions

* ref(ParticipantConnectionStatus): use for..of and () =>

* remove double 'l'

* ref: fix ESLint errors

* fix(MungeLocalSdp): adopt to new SdpTransformerUtil

* ref(MungeLocalSdp): use for .. of

* ref(SdpTransformUtil): remove "forEachSSRCAttr"

* fix(SDPDiffer): fix invalid "arrayEquals" call

* doc(MungeLocalSdp): add fixme

* fix(P2PEnabledConference): JVB tracks not added

* ref(JitsiConference): doc + rename mute methods

* ref(JitsiConference): adjust log level

* fix(JitsiConference): remove invalid eslint comments

Some mistake during rebase merge

* doc(JitsiConference): add FIXME

* ref(JitsiConferenceEventManager): stick to "tpc"

* ref(JitsiLocalTrack): use Set for "peerConnections"

* ref(JitsiLocalTrack): simplify expression

* ref(MungeLocalSdp): style + doc fixes

* ref: rename MungeLocalSdp to LocalSdpMunger

* ref(ParticipantConn..Status): rename method

* ref(SignalingLayerImpl): use Map and =>

* fix(strophe.jingle.js): minor style fixes + rename

* doc(XMPPEvents): typo

* doc(P2PEnabledConference): typo and style

* ref(P2PEnabledConference): rename methods

* ref(P2PEnabledConference): do not use "window"

* fix(P2PEnabledConference): cleanup deferred task

* ref(TPC): make options the last arg

* ref(TPC): use Map

* ref(TPC): syntax and other fixes...

* doc(ChatRoom): remove comment

* ref: remove P2PEnabledConference

* fix: remove JSUtil.js

* ref(LocalSdpMunger): re-use 'RtxModifier'

Reuses RtxModifier for injecting local RTX SSRCs as part of
the LocalSdpMunger logic.

* doc(LocalSdpMunger): remove confusing FIXME

* fix(TPC): setLocalDescription for FF

* fix(LocalSdpMunger): crash on react-native

* fix(JingleSessionPC): no events when ended

The instance once terminated should not emit connection state events.

* fix(P2P): do not start P2P on react-native

* fix: log meaningful error

Prior to this change you would see something like:
JitsiConference <error>: null

* fix(JingleSessionPC): no IQs once ended

* fix(JingleSessionPC): Jingle error logging

* fix: arguments order

* fix: make audio SSRC consistent

Audio SSRC needs to stay consistent between detach and attach operations
in order to avoid source-remove/source-add.

* fix(P2P): disable P2P on FF

There are problems with going back from P2P to JVB in FireFox. Other
participants will not see FF video. Looks like something related to
detach/attach.

* fix(JitsiConference): attach local tracks

Local tracks should be attached back to the JVB connection only
if the P2P was established.

* ref(JitsiConference): PR review fixes

ref(JitsiConference): else if

ref(JitsiConference): use getter

doc(JitsiConference): add comment

style(JitsiConference): remove extra line

log(JitsiConference): misleading msg

ref(JitsiConference): rename method

* ref(RTC): del _iteratePeerConnections

* ref: move getUfrag to SDPUtil

* fix(LocalSdpMunger): docs and if check

* fix(TPC): docs and typo

* ref(JingleSessionPC): PR review fixes

ref(JingleSessionPC): rename 'candidates'

ref(JitsiConference): remove extra check

ref(JitsiConference): rename isP2PEstablished

ref(JitsiConference): rename field (typo)

* doc(JitsiConferenceEventManager): typo

* ref(JitsiLocalTrack): rename var

* ref(JitsiConference): PR review fixes

ref(JitsiConference): rename var

doc(JitsiConference): add comment

doc(JitsiConference): add comment

doc(JitsiConference): fix comment

ref(JitsiConference): rename listener

ref(JitsiConference): rename var

* doc(RTC): remove duplicated arg description

doc(RTC): fill docs

* doc(SignalingLayerImpl): remove fixed FIXME

* ref(strophe.jingle.js): remove comment and break line

* style(TPC): formatting

doc(TPC): add FIXME

ref(TPC): remove unused code

doc(TPC): add docs

* doc(JingleSessionPC): mark "send" methods private

style(JingleSessionPC): extra lines
master
Paweł Domas 8 年前
父节点
当前提交
81a1b0a31b

+ 929
- 243
JitsiConference.js
文件差异内容过多而无法显示
查看文件


+ 69
- 71
JitsiConferenceEventManager.js 查看文件

@@ -29,6 +29,12 @@ function JitsiConferenceEventManager(conference) {
29 29
             conference.statistics.sendMuteEvent(track.isMuted(),
30 30
                 track.getType());
31 31
         });
32
+    conference.on(
33
+        JitsiConferenceEvents.CONNECTION_INTERRUPTED,
34
+        Statistics.sendEventToAll.bind(Statistics, 'connection.interrupted'));
35
+    conference.on(
36
+        JitsiConferenceEvents.CONNECTION_RESTORED,
37
+        Statistics.sendEventToAll.bind(Statistics, 'connection.restored'));
32 38
 }
33 39
 
34 40
 /**
@@ -111,11 +117,15 @@ JitsiConferenceEventManager.prototype.setupChatRoomListeners = function() {
111 117
     this.chatRoomForwarder = new EventEmitterForwarder(chatRoom,
112 118
         this.conference.eventEmitter);
113 119
 
114
-    chatRoom.addListener(XMPPEvents.ICE_RESTARTING, () => {
115
-        // All data channels have to be closed, before ICE restart
116
-        // otherwise Chrome will not trigger "opened" event for the channel
117
-        // established with the new bridge
118
-        conference.rtc.closeAllDataChannels();
120
+    chatRoom.addListener(XMPPEvents.ICE_RESTARTING, jingleSession => {
121
+        if (!jingleSession.isP2P) {
122
+            // All data channels have to be closed, before ICE restart
123
+            // otherwise Chrome will not trigger "opened" event for the channel
124
+            // established with the new bridge
125
+            conference.rtc.closeAllDataChannels();
126
+        }
127
+
128
+        // else: there are no DataChannels in P2P session (at least for now)
119 129
     });
120 130
 
121 131
     chatRoom.addListener(XMPPEvents.AUDIO_MUTED_BY_FOCUS,
@@ -140,7 +150,7 @@ JitsiConferenceEventManager.prototype.setupChatRoomListeners = function() {
140 150
     // send some analytics events
141 151
     chatRoom.addListener(XMPPEvents.MUC_JOINED,
142 152
         () => {
143
-            this.conference.connectionIsInterrupted = false;
153
+            this.conference.isJvbConnectionInterrupted = false;
144 154
 
145 155
             Object.keys(chatRoom.connectionTimes).forEach(key => {
146 156
                 const value = chatRoom.connectionTimes[key];
@@ -194,16 +204,16 @@ JitsiConferenceEventManager.prototype.setupChatRoomListeners = function() {
194 204
 
195 205
     chatRoom.addListener(XMPPEvents.JINGLE_FATAL_ERROR,
196 206
         (session, error) => {
197
-            conference.eventEmitter.emit(
198
-                JitsiConferenceEvents.CONFERENCE_FAILED,
199
-                JitsiConferenceErrors.JINGLE_FATAL_ERROR, error);
207
+            if (!session.isP2P) {
208
+                conference.eventEmitter.emit(
209
+                    JitsiConferenceEvents.CONFERENCE_FAILED,
210
+                    JitsiConferenceErrors.JINGLE_FATAL_ERROR, error);
211
+            }
200 212
         });
201 213
 
202 214
     chatRoom.addListener(XMPPEvents.CONNECTION_ICE_FAILED,
203
-        () => {
204
-            chatRoom.eventEmitter.emit(
205
-                XMPPEvents.CONFERENCE_SETUP_FAILED,
206
-                new Error('ICE fail'));
215
+        jingleSession => {
216
+            conference._onIceConnectionFailed(jingleSession);
207 217
         });
208 218
 
209 219
     this.chatRoomForwarder.forward(XMPPEvents.MUC_DESTROYED,
@@ -230,14 +240,10 @@ JitsiConferenceEventManager.prototype.setupChatRoomListeners = function() {
230 240
         = reason => Statistics.sendEventToAll(`conference.error.${reason}`);
231 241
 
232 242
     chatRoom.addListener(XMPPEvents.SESSION_ACCEPT_TIMEOUT,
233
-        eventLogHandler.bind(null, 'sessionAcceptTimeout'));
234
-
235
-    this.chatRoomForwarder.forward(XMPPEvents.CONNECTION_INTERRUPTED,
236
-        JitsiConferenceEvents.CONNECTION_INTERRUPTED);
237
-    chatRoom.addListener(XMPPEvents.CONNECTION_INTERRUPTED,
238
-        () => {
239
-            Statistics.sendEventToAll('connection.interrupted');
240
-            this.conference.connectionIsInterrupted = true;
243
+        jingleSession => {
244
+            eventLogHandler(
245
+                jingleSession.isP2P
246
+                    ? 'p2pSessionAcceptTimeout' : 'sessionAcceptTimeout');
241 247
         });
242 248
 
243 249
     this.chatRoomForwarder.forward(XMPPEvents.RECORDER_STATE_CHANGED,
@@ -246,18 +252,17 @@ JitsiConferenceEventManager.prototype.setupChatRoomListeners = function() {
246 252
     this.chatRoomForwarder.forward(XMPPEvents.PHONE_NUMBER_CHANGED,
247 253
         JitsiConferenceEvents.PHONE_NUMBER_CHANGED);
248 254
 
249
-    this.chatRoomForwarder.forward(XMPPEvents.CONNECTION_RESTORED,
250
-        JitsiConferenceEvents.CONNECTION_RESTORED);
251
-    chatRoom.addListener(XMPPEvents.CONNECTION_RESTORED,
252
-        () => {
253
-            Statistics.sendEventToAll('connection.restored');
254
-            this.conference.connectionIsInterrupted = false;
255
+    chatRoom.addListener(
256
+        XMPPEvents.CONFERENCE_SETUP_FAILED,
257
+        (jingleSession, error) => {
258
+            if (!jingleSession.isP2P) {
259
+                conference.eventEmitter.emit(
260
+                    JitsiConferenceEvents.CONFERENCE_FAILED,
261
+                    JitsiConferenceErrors.SETUP_FAILED,
262
+                    error);
263
+            }
255 264
         });
256 265
 
257
-    this.chatRoomForwarder.forward(XMPPEvents.CONFERENCE_SETUP_FAILED,
258
-        JitsiConferenceEvents.CONFERENCE_FAILED,
259
-        JitsiConferenceErrors.SETUP_FAILED);
260
-
261 266
     chatRoom.setParticipantPropertyListener((node, from) => {
262 267
         const participant = conference.getParticipantById(from);
263 268
 
@@ -294,8 +299,7 @@ JitsiConferenceEventManager.prototype.setupChatRoomListeners = function() {
294 299
         conference.onDisplayNameChanged.bind(conference));
295 300
 
296 301
     chatRoom.addListener(XMPPEvents.LOCAL_ROLE_CHANGED, role => {
297
-        conference.eventEmitter.emit(JitsiConferenceEvents.USER_ROLE_CHANGED,
298
-            conference.myUserId(), role);
302
+        conference.onLocalRoleChanged(role);
299 303
 
300 304
         // log all events for the recorder operated by the moderator
301 305
         if (conference.statistics && conference.isModerator()) {
@@ -351,19 +355,6 @@ JitsiConferenceEventManager.prototype.setupChatRoomListeners = function() {
351 355
                 JitsiConferenceEvents.USER_STATUS_CHANGED, id, status);
352 356
         });
353 357
 
354
-    conference.room.addListener(XMPPEvents.LOCAL_UFRAG_CHANGED,
355
-        ufrag => {
356
-            Statistics.sendLog(
357
-                JSON.stringify({ id: 'local_ufrag',
358
-                    value: ufrag }));
359
-        });
360
-    conference.room.addListener(XMPPEvents.REMOTE_UFRAG_CHANGED,
361
-        ufrag => {
362
-            Statistics.sendLog(
363
-                JSON.stringify({ id: 'remote_ufrag',
364
-                    value: ufrag }));
365
-        });
366
-
367 358
     chatRoom.addPresenceListener('startmuted', (data, from) => {
368 359
         let isModerator = false;
369 360
 
@@ -404,20 +395,6 @@ JitsiConferenceEventManager.prototype.setupChatRoomListeners = function() {
404 395
         }
405 396
     });
406 397
 
407
-    chatRoom.addPresenceListener('videomuted', (values, from) => {
408
-        conference.rtc.handleRemoteTrackMute(MediaType.VIDEO,
409
-            values.value === 'true', from);
410
-    });
411
-
412
-    chatRoom.addPresenceListener('audiomuted', (values, from) => {
413
-        conference.rtc.handleRemoteTrackMute(MediaType.AUDIO,
414
-            values.value === 'true', from);
415
-    });
416
-
417
-    chatRoom.addPresenceListener('videoType', (data, from) => {
418
-        conference.rtc.handleRemoteTrackVideoTypeChanged(data.value, from);
419
-    });
420
-
421 398
     chatRoom.addPresenceListener('devices', (data, from) => {
422 399
         let isAudioAvailable = false;
423 400
         let isVideoAvailable = false;
@@ -467,7 +444,7 @@ JitsiConferenceEventManager.prototype.setupChatRoomListeners = function() {
467 444
     if (conference.statistics) {
468 445
         // FIXME ICE related events should end up in RTCEvents eventually
469 446
         chatRoom.addListener(XMPPEvents.CONNECTION_ICE_FAILED,
470
-            pc => {
447
+            (session, pc) => {
471 448
                 conference.statistics.sendIceConnectionFailedEvent(pc);
472 449
             });
473 450
         chatRoom.addListener(XMPPEvents.ADD_ICE_CANDIDATE_FAILED,
@@ -543,6 +520,27 @@ JitsiConferenceEventManager.prototype.setupRTCListeners = function() {
543 520
             }
544 521
         });
545 522
 
523
+    rtc.addListener(RTCEvents.LOCAL_UFRAG_CHANGED,
524
+        (tpc, ufrag) => {
525
+            if (!tpc.isP2P) {
526
+                Statistics.sendLog(
527
+                    JSON.stringify({
528
+                        id: 'local_ufrag',
529
+                        value: ufrag
530
+                    }));
531
+            }
532
+        });
533
+    rtc.addListener(RTCEvents.REMOTE_UFRAG_CHANGED,
534
+        (tpc, ufrag) => {
535
+            if (!tpc.isP2P) {
536
+                Statistics.sendLog(
537
+                    JSON.stringify({
538
+                        id: 'remote_ufrag',
539
+                        value: ufrag
540
+                    }));
541
+            }
542
+        });
543
+
546 544
     if (conference.statistics) {
547 545
         rtc.addListener(RTCEvents.CREATE_ANSWER_FAILED,
548 546
             (e, pc) => {
@@ -587,6 +585,12 @@ JitsiConferenceEventManager.prototype.setupXMPPListeners = function() {
587 585
     conference.xmpp.addListener(
588 586
         XMPPEvents.CALL_INCOMING,
589 587
         conference.onIncomingCall.bind(conference));
588
+    conference.xmpp.addListener(
589
+        XMPPEvents.CALL_ACCEPTED,
590
+        conference.onCallAccepted.bind(conference));
591
+    conference.xmpp.addListener(
592
+        XMPPEvents.TRANSPORT_INFO,
593
+        conference.onTransportInfo.bind(conference));
590 594
     conference.xmpp.addListener(
591 595
         XMPPEvents.CALL_ENDED,
592 596
         conference.onCallEnded.bind(conference));
@@ -624,13 +628,7 @@ JitsiConferenceEventManager.prototype.setupStatisticsListeners = function() {
624 628
     }
625 629
 
626 630
     conference.statistics.addAudioLevelListener((ssrc, level) => {
627
-        const resource = conference.rtc.getResourceBySSRC(ssrc);
628
-
629
-        if (!resource) {
630
-            return;
631
-        }
632
-
633
-        conference.rtc.setAudioLevel(resource, level);
631
+        conference.rtc.setAudioLevel(ssrc, level);
634 632
     });
635 633
 
636 634
     // Forward the "before stats disposed" event
@@ -647,15 +645,15 @@ JitsiConferenceEventManager.prototype.setupStatisticsListeners = function() {
647 645
             JitsiConferenceEvents.CONNECTION_STATS, stats);
648 646
     });
649 647
 
650
-    conference.statistics.addByteSentStatsListener(stats => {
648
+    conference.statistics.addByteSentStatsListener((tpc, stats) => {
651 649
         conference.getLocalTracks(MediaType.AUDIO).forEach(track => {
652
-            const ssrc = track.getSSRC();
650
+            const ssrc = tpc.getLocalSSRC(track);
653 651
 
654 652
             if (!ssrc || !stats.hasOwnProperty(ssrc)) {
655 653
                 return;
656 654
             }
657 655
 
658
-            track._setByteSent(stats[ssrc]);
656
+            track._setByteSent(tpc, stats[ssrc]);
659 657
         });
660 658
     });
661 659
 };

+ 7
- 0
JitsiConferenceEvents.js 查看文件

@@ -132,6 +132,13 @@ export const PARTCIPANT_FEATURES_CHANGED
132 132
 export const PARTICIPANT_PROPERTY_CHANGED
133 133
     = 'conference.participant_property_changed';
134 134
 
135
+/**
136
+ * Indicates that the conference has switched between JVB and P2P connections.
137
+ * The first argument of this event is a <tt>boolean</tt> which when set to
138
+ * <tt>true</tt> means that the conference is running on the P2P connection.
139
+ */
140
+export const P2P_STATUS = 'conference.p2pStatus';
141
+
135 142
 /**
136 143
  * Indicates that phone number changed.
137 144
  */

+ 6
- 5
JitsiConnection.js 查看文件

@@ -1,8 +1,7 @@
1
-const JitsiConference = require('./JitsiConference');
2
-
1
+import JitsiConference from './JitsiConference';
3 2
 import * as JitsiConnectionEvents from './JitsiConnectionEvents';
3
+import Statistics from './modules/statistics/statistics';
4 4
 import XMPP from './modules/xmpp/xmpp';
5
-const Statistics = require('./modules/statistics/statistics');
6 5
 
7 6
 /**
8 7
  * Creates new connection object for the Jitsi Meet server side video
@@ -90,9 +89,11 @@ JitsiConnection.prototype.setToken = function(token) {
90 89
  * @returns {JitsiConference} returns the new conference object.
91 90
  */
92 91
 JitsiConnection.prototype.initJitsiConference = function(name, options) {
93
-    return new JitsiConference({ name,
92
+    return new JitsiConference({
93
+        name,
94 94
         config: options,
95
-        connection: this });
95
+        connection: this
96
+    });
96 97
 };
97 98
 
98 99
 /**

+ 111
- 62
modules/RTC/JitsiLocalTrack.js 查看文件

@@ -20,6 +20,7 @@ const logger = getLogger(__filename);
20 20
 /**
21 21
  * Represents a single media track(either audio or video).
22 22
  * One <tt>JitsiLocalTrack</tt> corresponds to one WebRTC MediaStreamTrack.
23
+ * @param {number} rtcId the ID assigned by the RTC module
23 24
  * @param stream WebRTC MediaStream, parent of the track
24 25
  * @param track underlying WebRTC MediaStreamTrack for new JitsiRemoteTrack
25 26
  * @param mediaType the MediaType of the JitsiRemoteTrack
@@ -30,6 +31,7 @@ const logger = getLogger(__filename);
30 31
  * @constructor
31 32
  */
32 33
 function JitsiLocalTrack(
34
+        rtcId,
33 35
         stream,
34 36
         track,
35 37
         mediaType,
@@ -37,6 +39,12 @@ function JitsiLocalTrack(
37 39
         resolution,
38 40
         deviceId,
39 41
         facingMode) {
42
+
43
+    /**
44
+     * The ID assigned by the RTC module on instance creation.
45
+     * @type {number}
46
+     */
47
+    this.rtcId = rtcId;
40 48
     JitsiTrack.call(
41 49
         this,
42 50
         null /* RTC */,
@@ -49,8 +57,7 @@ function JitsiLocalTrack(
49 57
             this.dontFireRemoveEvent = false;
50 58
         } /* inactiveHandler */,
51 59
         mediaType,
52
-        videoType,
53
-        null /* ssrc */);
60
+        videoType);
54 61
     this.dontFireRemoveEvent = false;
55 62
     this.resolution = resolution;
56 63
 
@@ -62,9 +69,16 @@ function JitsiLocalTrack(
62 69
 
63 70
     this.deviceId = deviceId;
64 71
     this.startMuted = false;
65
-    this.initialMSID = this.getMSID();
72
+    this.storedMSID = this.getMSID();
66 73
     this.inMuteOrUnmuteProgress = false;
67 74
 
75
+    /**
76
+     * An array which stores the peer connection to which this local track is
77
+     * currently attached to. See {@link TraceablePeerConnection.attachTrack}.
78
+     * @type {Set<TraceablePeerConnection>}
79
+     */
80
+    this.peerConnections = new Set();
81
+
68 82
     /**
69 83
      * The facing mode of the camera from which this JitsiLocalTrack instance
70 84
      * was obtained.
@@ -144,6 +158,34 @@ function JitsiLocalTrack(
144 158
 JitsiLocalTrack.prototype = Object.create(JitsiTrack.prototype);
145 159
 JitsiLocalTrack.prototype.constructor = JitsiLocalTrack;
146 160
 
161
+JitsiLocalTrack.prototype._addPeerConnection = function(tpc) {
162
+    if (this._isAttachedToPC(tpc)) {
163
+        logger.error(`${tpc} has been associated with ${this} already !`);
164
+    } else {
165
+        this.peerConnections.add(tpc);
166
+    }
167
+};
168
+
169
+JitsiLocalTrack.prototype._removePeerConnection = function(tpc) {
170
+    if (this._isAttachedToPC(tpc)) {
171
+        this.peerConnections.delete(tpc);
172
+    } else {
173
+        logger.error(`${tpc} is not associated with ${this}`);
174
+    }
175
+};
176
+
177
+/**
178
+ * Checks whether or not this instance is attached to given
179
+ * <tt>TraceablePeerConnection</tt>. See
180
+ * {@link TraceablePeerConnection.attachTrack} for more info.
181
+ * @param {TraceablePeerConnection.attachTrack} tpc
182
+ * @return {boolean} <tt>true</tt> if this tracks is currently attached to given
183
+ * peer connection or <tt>false</tt> otherwise.
184
+ */
185
+JitsiLocalTrack.prototype._isAttachedToPC = function(tpc) {
186
+    return this.peerConnections.has(tpc);
187
+};
188
+
147 189
 /**
148 190
  * Returns if associated MediaStreamTrack is in the 'ended' state
149 191
  * @returns {boolean}
@@ -237,6 +279,23 @@ JitsiLocalTrack.prototype._setRealDeviceIdFromDeviceList = function(devices) {
237 279
     }
238 280
 };
239 281
 
282
+/**
283
+ * Sets the stream property of JitsiLocalTrack object and sets all stored
284
+ * handlers to it.
285
+ * @param {MediaStream} stream the new stream.
286
+ */
287
+JitsiLocalTrack.prototype._setStream = function(stream) {
288
+    JitsiTrack.prototype._setStream.call(this, stream);
289
+
290
+    // Store the MSID for video mute/unmute purposes
291
+    if (stream) {
292
+        this.storedMSID = this.getMSID();
293
+        logger.debug(`Setting new MSID: ${this.storedMSID} on ${this}`);
294
+    } else {
295
+        logger.debug(`Setting 'null' stream on ${this}`);
296
+    }
297
+};
298
+
240 299
 /**
241 300
  * Mutes the track. Will reject the Promise if there is mute/unmute operation
242 301
  * in progress.
@@ -300,22 +359,30 @@ JitsiLocalTrack.prototype._setMute = function(mute) {
300 359
     // Local track can be used out of conference, so we need to handle that
301 360
     // case and mark that track should start muted or not when added to
302 361
     // conference.
362
+    // Pawel: track's muted status should be taken into account when track is
363
+    // being added to the conference/JingleSessionPC/TraceablePeerConnection.
364
+    // There's no need to add such fields. It is logical that when muted track
365
+    // is being added to a conference it "starts muted"...
303 366
     if (!this.conference || !this.conference.room) {
304 367
         this.startMuted = mute;
305 368
     }
306 369
 
307 370
     this.dontFireRemoveEvent = false;
308 371
 
309
-    // FIXME FF does not support 'removeStream' method used to mute
372
+    // A function that will print info about muted status transition
373
+    const logMuteInfo = () => logger.info(`Mute ${this}: ${mute}`);
374
+
310 375
     if (this.isAudioTrack()
311 376
         || this.videoType === VideoType.DESKTOP
312
-        || RTCBrowserType.isFirefox()) {
377
+        || !RTCBrowserType.doesVideoMuteByStreamRemove()) {
378
+        logMuteInfo();
313 379
         if (this.track) {
314 380
             this.track.enabled = !mute;
315 381
         }
316 382
     } else if (mute) {
317 383
         this.dontFireRemoveEvent = true;
318 384
         promise = new Promise((resolve, reject) => {
385
+            logMuteInfo();
319 386
             this._removeStreamFromConferenceAsMute(() => {
320 387
                 // FIXME: Maybe here we should set the SRC for the containers
321 388
                 // to something
@@ -327,6 +394,8 @@ JitsiLocalTrack.prototype._setMute = function(mute) {
327 394
             });
328 395
         });
329 396
     } else {
397
+        logMuteInfo();
398
+
330 399
         // This path is only for camera.
331 400
         const streamOptions = {
332 401
             cameraDeviceId: this.getDeviceId(),
@@ -352,7 +421,7 @@ JitsiLocalTrack.prototype._setMute = function(mute) {
352 421
                     // unmute, but let's not crash here
353 422
                     if (self.videoType !== streamInfo.videoType) {
354 423
                         logger.warn(
355
-                            'Video type has changed after unmute!',
424
+                            `${this}: video type has changed after unmute!`,
356 425
                             self.videoType, streamInfo.videoType);
357 426
                         self.videoType = streamInfo.videoType;
358 427
                     }
@@ -387,18 +456,18 @@ JitsiLocalTrack.prototype._addStreamToConferenceAsUnmute = function() {
387 456
         return Promise.resolve();
388 457
     }
389 458
 
459
+    // FIXME it would be good to not included conference as part of this process
460
+    // Only TraceablePeerConnections to which the track is attached should care
461
+    // about this action. The TPCs to which the track is not attached can sync
462
+    // up when track is re-attached.
463
+    // A problem with that is that the "modify sources" queue is part of
464
+    // the JingleSessionPC and it would be excluded from the process. One
465
+    // solution would be to extract class between TPC and JingleSessionPC which
466
+    // would contain the queue and would notify the signaling layer when local
467
+    // SSRCs are changed. This would help to separate XMPP from the RTC module.
390 468
     return new Promise((resolve, reject) => {
391
-        this.conference._addLocalStream(
392
-            this.stream,
393
-            resolve,
394
-            error => reject(new Error(error)),
395
-            {
396
-                mtype: this.type,
397
-                type: 'unmute',
398
-                ssrcs: this.ssrc && this.ssrc.ssrcs,
399
-                groups: this.ssrc && this.ssrc.groups,
400
-                msid: this.getMSID()
401
-            });
469
+        this.conference._addLocalTrackAsUnmute(this)
470
+            .then(resolve, error => reject(new Error(error)));
402 471
     });
403 472
 };
404 473
 
@@ -415,17 +484,9 @@ JitsiLocalTrack.prototype._removeStreamFromConferenceAsMute
415 484
 
416 485
         return;
417 486
     }
418
-
419
-    this.conference.removeLocalStream(
420
-        this.stream,
487
+    this.conference._removeLocalTrackAsMute(this).then(
421 488
         successCallback,
422
-        error => errorCallback(new Error(error)),
423
-        {
424
-            mtype: this.type,
425
-            type: 'mute',
426
-            ssrcs: this.ssrc && this.ssrc.ssrcs,
427
-            groups: this.ssrc && this.ssrc.groups
428
-        });
489
+        error => errorCallback(new Error(error)));
429 490
 };
430 491
 
431 492
 /**
@@ -502,15 +563,6 @@ JitsiLocalTrack.prototype.isMuted = function() {
502 563
 
503 564
 };
504 565
 
505
-/**
506
- * Updates the SSRC associated with the MediaStream in JitsiLocalTrack object.
507
- * @ssrc the new ssrc
508
- */
509
-JitsiLocalTrack.prototype._setSSRC = function(ssrc) {
510
-    this.ssrc = ssrc;
511
-};
512
-
513
-
514 566
 /**
515 567
  * Sets the JitsiConference object associated with the track. This is temp
516 568
  * solution.
@@ -528,24 +580,6 @@ JitsiLocalTrack.prototype._setConference = function(conference) {
528 580
     }
529 581
 };
530 582
 
531
-/**
532
- * Gets the SSRC of this local track if it's available already or <tt>null</tt>
533
- * otherwise. That's because we don't know the SSRC until local description is
534
- * created.
535
- * In case of video and simulcast returns the the primarySSRC.
536
- * @returns {string} or {null}
537
- */
538
-JitsiLocalTrack.prototype.getSSRC = function() {
539
-    if (this.ssrc && this.ssrc.groups && this.ssrc.groups.length) {
540
-        return this.ssrc.groups[0].ssrcs[0];
541
-    } else if (this.ssrc && this.ssrc.ssrcs && this.ssrc.ssrcs.length) {
542
-        return this.ssrc.ssrcs[0];
543
-    }
544
-
545
-    return null;
546
-
547
-};
548
-
549 583
 /**
550 584
  * Returns <tt>true</tt>.
551 585
  * @returns {boolean} <tt>true</tt>
@@ -562,23 +596,30 @@ JitsiLocalTrack.prototype.getDeviceId = function() {
562 596
     return this._realDeviceId || this.deviceId;
563 597
 };
564 598
 
599
+/**
600
+ * Returns the participant id which owns the track.
601
+ * @returns {string} the id of the participants. It corresponds to the Colibri
602
+ * endpoint id/MUC nickname in case of Jitsi-meet.
603
+ */
604
+JitsiLocalTrack.prototype.getParticipantId = function() {
605
+    return this.conference && this.conference.myUserId();
606
+};
607
+
565 608
 /**
566 609
  * Sets the value of bytes sent statistic.
567
- * @param bytesSent {integer} the new value (FIXME: what is an integer in js?)
610
+ * @param {TraceablePeerConnection} tpc the source of the "bytes sent" stat
611
+ * @param {number} bytesSent the new value
568 612
  * NOTE: used only for audio tracks to detect audio issues.
569 613
  */
570
-JitsiLocalTrack.prototype._setByteSent = function(bytesSent) {
614
+JitsiLocalTrack.prototype._setByteSent = function(tpc, bytesSent) {
571 615
     this._bytesSent = bytesSent;
572
-
573
-    // FIXME it's a shame that PeerConnection and ICE status does not belong
574
-    // to the RTC module and it has to be accessed through
575
-    // the conference(and through the XMPP chat room ???) instead
576
-    const iceConnectionState
577
-        = this.conference ? this.conference.getConnectionState() : null;
616
+    const iceConnectionState = tpc.getConnectionState();
578 617
 
579 618
     if (this._testByteSent && iceConnectionState === 'connected') {
580 619
         setTimeout(() => {
581 620
             if (this._bytesSent <= 0) {
621
+                logger.warn(`${this} 'bytes sent' <= 0: ${this._bytesSent}`);
622
+
582 623
                 // we are not receiving anything from the microphone
583 624
                 this._fireNoDataFromSourceEvent();
584 625
             }
@@ -676,4 +717,12 @@ JitsiLocalTrack.prototype._isReceivingData = function() {
676 717
             && (!('muted' in track) || track.muted !== true));
677 718
 };
678 719
 
720
+/**
721
+ * Creates a text representation of this local track instance.
722
+ * @return {string}
723
+ */
724
+JitsiLocalTrack.prototype.toString = function() {
725
+    return `LocalTrack[${this.rtcId},${this.getType()}]`;
726
+};
727
+
679 728
 module.exports = JitsiLocalTrack;

+ 16
- 3
modules/RTC/JitsiRemoteTrack.js 查看文件

@@ -24,6 +24,8 @@ let ttfmTrackerVideoAttached = false;
24 24
  * @param {VideoType} videoType the type of the video if applicable
25 25
  * @param {string} ssrc the SSRC number of the Media Stream
26 26
  * @param {boolean} muted the initial muted state
27
+ * @param {boolean} isP2P indicates whether or not this track belongs to a P2P
28
+ * session
27 29
  * @constructor
28 30
  */
29 31
 function JitsiRemoteTrack(
@@ -35,7 +37,8 @@ function JitsiRemoteTrack(
35 37
         mediaType,
36 38
         videoType,
37 39
         ssrc,
38
-        muted) {
40
+        muted,
41
+        isP2P) {
39 42
     JitsiTrack.call(
40 43
         this,
41 44
         conference,
@@ -45,11 +48,12 @@ function JitsiRemoteTrack(
45 48
             // Nothing to do if the track is inactive.
46 49
         },
47 50
         mediaType,
48
-        videoType,
49
-        ssrc);
51
+        videoType);
50 52
     this.rtc = rtc;
53
+    this.ssrc = ssrc;
51 54
     this.ownerEndpointId = ownerEndpointId;
52 55
     this.muted = muted;
56
+    this.isP2P = isP2P;
53 57
 
54 58
     // we want to mark whether the track has been ever muted
55 59
     // to detect ttfm events for startmuted conferences, as it can significantly
@@ -218,4 +222,13 @@ JitsiRemoteTrack.prototype._attachTTFMTracker = function(container) {
218 222
     }
219 223
 };
220 224
 
225
+/**
226
+ * Creates a text representation of this remote track instance.
227
+ * @return {string}
228
+ */
229
+JitsiRemoteTrack.prototype.toString = function() {
230
+    return `RemoteTrack[${this.ownerEndpointId}, ${this.getType()
231
+            }, p2p: ${this.isP2P}]`;
232
+};
233
+
221 234
 module.exports = JitsiRemoteTrack;

+ 6
- 9
modules/RTC/JitsiTrack.js 查看文件

@@ -67,16 +67,14 @@ function addMediaStreamInactiveHandler(mediaStream, handler) {
67 67
  *        onended/oninactive events of the stream.
68 68
  * @param trackMediaType the media type of the JitsiTrack
69 69
  * @param videoType the VideoType for this track if any
70
- * @param ssrc the SSRC of this track if known
71 70
  */
72 71
 function JitsiTrack(
73
-        conference,
74
-        stream,
75
-        track,
76
-        streamInactiveHandler,
77
-        trackMediaType,
78
-        videoType,
79
-        ssrc) {
72
+    conference,
73
+    stream,
74
+    track,
75
+    streamInactiveHandler,
76
+    trackMediaType,
77
+    videoType) {
80 78
     /**
81 79
      * Array with the HTML elements that are displaying the streams.
82 80
      * @type {Array}
@@ -84,7 +82,6 @@ function JitsiTrack(
84 82
     this.containers = [];
85 83
     this.conference = conference;
86 84
     this.stream = stream;
87
-    this.ssrc = ssrc;
88 85
     this.eventEmitter = new EventEmitter();
89 86
     this.audioLevel = -1;
90 87
     this.type = trackMediaType;

+ 298
- 0
modules/RTC/LocalSdpMunger.js 查看文件

@@ -0,0 +1,298 @@
1
+/* global __filename */
2
+
3
+import { getLogger } from 'jitsi-meet-logger';
4
+import * as MediaType from '../../service/RTC/MediaType';
5
+import { SdpTransformWrap } from '../xmpp/SdpTransformUtil';
6
+
7
+const logger = getLogger(__filename);
8
+
9
+/**
10
+ * Fakes local SDP, so that it will reflect detached local tracks associated
11
+ * with the {@link TraceablePeerConnection} and make operations like
12
+ * attach/detach and video mute/unmute local operations. That means it prevents
13
+ * from SSRC updates being sent to Jicofo/remote peer, so that there is no
14
+ * sRD/sLD cycle on the remote side.
15
+ */
16
+export default class LocalSdpMunger {
17
+
18
+    /**
19
+     * Creates new <tt>LocalSdpMunger</tt> instance.
20
+     *
21
+     * @param {TraceablePeerConnection} tpc
22
+     */
23
+    constructor(tpc) {
24
+        this.tpc = tpc;
25
+    }
26
+
27
+    /**
28
+     * Makes sure that detached local audio tracks stored in the parent
29
+     * {@link TraceablePeerConnection} are described in the local SDP.
30
+     * It's done in order to prevent from sending 'source-remove'/'source-add'
31
+     * Jingle notifications when local audio track is detached from
32
+     * the {@link TraceablePeerConnection}.
33
+     * @param {SdpTransformWrap} transformer the transformer instance which will
34
+     * be used to process the SDP.
35
+     * @return {boolean} <tt>true</tt> if there were any modifications to
36
+     * the SDP wrapped by <tt>transformer</tt>.
37
+     * @private
38
+     */
39
+    _addDetachedLocalAudioTracksToSDP(transformer) {
40
+        const localAudio = this.tpc.getLocalTracks(MediaType.AUDIO);
41
+
42
+        if (!localAudio.length) {
43
+            return false;
44
+        }
45
+        const audioMLine = transformer.selectMedia('audio');
46
+
47
+        if (!audioMLine) {
48
+            logger.error(
49
+                'Unable to hack local audio track SDP - no "audio" media');
50
+
51
+            return false;
52
+        }
53
+
54
+        if (audioMLine.direction === 'inactive') {
55
+            logger.error(
56
+                'Not doing local audio transform for "inactive" direction');
57
+
58
+            return false;
59
+        }
60
+
61
+        let modified = false;
62
+
63
+        for (const audioTrack of localAudio) {
64
+            const isAttached = audioTrack._isAttachedToPC(this.tpc);
65
+            const shouldFake = !isAttached;
66
+
67
+            logger.debug(
68
+                `${audioTrack} isAttached: ${isAttached
69
+                    } => should fake audio SDP ?: ${shouldFake}`);
70
+
71
+            if (!shouldFake) {
72
+                // not using continue increases indentation
73
+                // eslint-disable-next-line no-continue
74
+                continue;
75
+            }
76
+
77
+            // Inject removed SSRCs
78
+            const audioSSRC = this.tpc.getLocalSSRC(audioTrack);
79
+            const audioMSID = audioTrack.storedMSID;
80
+
81
+            if (!audioSSRC) {
82
+                logger.error(
83
+                    `Can't fake SDP for ${audioTrack} - no SSRC stored`);
84
+
85
+                // Aborts the forEach on this particular track,
86
+                // but will continue with the other ones
87
+                // eslint-disable-next-line no-continue
88
+                continue;
89
+            } else if (!audioMSID) {
90
+                logger.error(
91
+                    `No MSID stored for local audio SSRC: ${audioSSRC}`);
92
+
93
+                // eslint-disable-next-line no-continue
94
+                continue;
95
+            }
96
+
97
+            if (audioMLine.getSSRCCount() > 0) {
98
+                logger.debug(
99
+                    'Doing nothing - audio SSRCs are still there');
100
+
101
+                // audio SSRCs are still there
102
+                // eslint-disable-next-line no-continue
103
+                continue;
104
+            }
105
+
106
+            modified = true;
107
+
108
+            // We need to fake sendrecv
109
+            audioMLine.direction = 'sendrecv';
110
+
111
+            logger.debug(`Injecting audio SSRC: ${audioSSRC}`);
112
+            audioMLine.addSSRCAttribute({
113
+                id: audioSSRC,
114
+                attribute: 'cname',
115
+                value: `injected-${audioSSRC}`
116
+            });
117
+            audioMLine.addSSRCAttribute({
118
+                id: audioSSRC,
119
+                attribute: 'msid',
120
+                value: audioMSID
121
+            });
122
+        }
123
+
124
+        return modified;
125
+    }
126
+
127
+    /**
128
+     * Makes sure that detached (or muted) local video tracks associated with
129
+     * the parent {@link TraceablePeerConnection} are described in the local
130
+     * SDP. It's done in order to prevent from sending
131
+     * 'source-remove'/'source-add' Jingle notifications when local video track
132
+     * is detached from the {@link TraceablePeerConnection} (or muted).
133
+     *
134
+     * NOTE 1 video track is assumed
135
+     *
136
+     * @param {SdpTransformWrap} transformer the transformer instance which will
137
+     * be used to process the SDP.
138
+     * @return {boolean} <tt>true</tt> if there were any modifications to
139
+     * the SDP wrapped by <tt>transformer</tt>.
140
+     * @private
141
+     */
142
+    _addDetachedLocalVideoTracksToSDP(transformer) {
143
+        // Go over each video tracks and check if the SDP has to be changed
144
+        const localVideos = this.tpc.getLocalTracks(MediaType.VIDEO);
145
+
146
+        if (!localVideos.length) {
147
+            return false;
148
+        } else if (localVideos.length !== 1) {
149
+            logger.error(
150
+                'There is more than 1 video track ! '
151
+                    + 'Strange things may happen !', localVideos);
152
+        }
153
+
154
+        const videoMLine = transformer.selectMedia('video');
155
+
156
+        if (!videoMLine) {
157
+            logger.error(
158
+                'Unable to hack local video track SDP - no "video" media');
159
+
160
+            return false;
161
+        }
162
+
163
+        if (videoMLine.direction === 'inactive') {
164
+            logger.error(
165
+                'Not doing local video transform for "inactive" direction.');
166
+
167
+            return false;
168
+        }
169
+
170
+        let modified = false;
171
+
172
+        for (const videoTrack of localVideos) {
173
+            const isMuted = videoTrack.isMuted();
174
+            const muteInProgress = videoTrack.inMuteOrUnmuteProgress;
175
+            const isAttached = videoTrack._isAttachedToPC(this.tpc);
176
+            const shouldFakeSdp = isMuted || muteInProgress || !isAttached;
177
+
178
+            logger.debug(
179
+                `${videoTrack
180
+                 } isMuted: ${isMuted
181
+                 }, is mute in progress: ${muteInProgress
182
+                 }, is attached ? : ${isAttached
183
+                 } => should fake sdp ? : ${shouldFakeSdp}`);
184
+
185
+            if (!shouldFakeSdp) {
186
+                // eslint-disable-next-line no-continue
187
+                continue;
188
+            }
189
+
190
+            // Inject removed SSRCs
191
+            const requiredSSRCs
192
+                = this.tpc.isSimulcastOn()
193
+                    ? this.tpc.simulcast.ssrcCache
194
+                    : [ this.tpc.sdpConsistency.cachedPrimarySsrc ];
195
+
196
+            if (!requiredSSRCs.length) {
197
+                logger.error(
198
+                    `No SSRCs stored for: ${videoTrack} in ${this.tpc}`);
199
+
200
+                // eslint-disable-next-line no-continue
201
+                continue;
202
+            }
203
+            if (!videoMLine.getSSRCCount()) {
204
+                logger.error(
205
+                    'No video SSRCs found '
206
+                        + '(should be at least the recv-only one');
207
+
208
+                // eslint-disable-next-line no-continue
209
+                continue;
210
+            }
211
+
212
+            modified = true;
213
+
214
+            // We need to fake sendrecv
215
+            videoMLine.direction = 'sendrecv';
216
+
217
+            // Check if the recvonly has MSID
218
+            const primarySSRC = requiredSSRCs[0];
219
+
220
+            // FIXME the cname could come from the stream, but may
221
+            // turn out to be too complex. It is fine to come up
222
+            // with any value, as long as we only care about
223
+            // the actual SSRC values when deciding whether or not
224
+            // an update should be sent
225
+            const primaryCname = `injected-${primarySSRC}`;
226
+
227
+            for (const ssrcNum of requiredSSRCs) {
228
+                // Remove old attributes
229
+                videoMLine.removeSSRC(ssrcNum);
230
+
231
+                // Inject
232
+                logger.debug(
233
+                    `Injecting video SSRC: ${ssrcNum} for ${videoTrack}`);
234
+                videoMLine.addSSRCAttribute({
235
+                    id: ssrcNum,
236
+                    attribute: 'cname',
237
+                    value: primaryCname
238
+                });
239
+                videoMLine.addSSRCAttribute({
240
+                    id: ssrcNum,
241
+                    attribute: 'msid',
242
+                    value: videoTrack.storedMSID
243
+                });
244
+            }
245
+            if (requiredSSRCs.length > 1) {
246
+                const group = {
247
+                    ssrcs: requiredSSRCs.join(' '),
248
+                    semantics: 'SIM'
249
+                };
250
+
251
+                if (!videoMLine.findGroup(group.semantics, group.ssrcs)) {
252
+                    // Inject the group
253
+                    logger.debug(
254
+                        `Injecting SIM group for ${videoTrack}`, group);
255
+                    videoMLine.addSSRCGroup(group);
256
+                }
257
+            }
258
+
259
+            // Insert RTX
260
+            // FIXME in P2P RTX is used by Chrome regardless of config option
261
+            // status. Because of that 'source-remove'/'source-add'
262
+            // notifications are still sent to remove/add RTX SSRC and FID group
263
+            if (!this.tpc.options.disableRtx) {
264
+                this.tpc.rtxModifier.modifyRtxSsrcs2(videoMLine);
265
+            }
266
+        }
267
+
268
+        return modified;
269
+    }
270
+
271
+    /**
272
+     * Maybe modifies local description to fake local tracks SDP when those are
273
+     * either muted or detached from the <tt>PeerConnection</tt>.
274
+     *
275
+     * @param {object} desc the WebRTC SDP object instance for the local
276
+     * description.
277
+     */
278
+    maybeMungeLocalSdp(desc) {
279
+        // Nothing to be done in early stage when localDescription
280
+        // is not available yet
281
+        if (!desc || !desc.sdp) {
282
+            return;
283
+        }
284
+
285
+        const transformer = new SdpTransformWrap(desc.sdp);
286
+        let modified = this._addDetachedLocalAudioTracksToSDP(transformer);
287
+
288
+        if (this._addDetachedLocalVideoTracksToSDP(transformer)) {
289
+            modified = true;
290
+        }
291
+        if (modified) {
292
+            // Write
293
+            desc.sdp = transformer.toRawSDP();
294
+
295
+            // logger.info("Post TRANSFORM: ", desc.sdp);
296
+        }
297
+    }
298
+}

+ 65
- 219
modules/RTC/RTC.js 查看文件

@@ -3,7 +3,6 @@ import DataChannels from './DataChannels';
3 3
 import { getLogger } from 'jitsi-meet-logger';
4 4
 import GlobalOnErrorHandler from '../util/GlobalOnErrorHandler';
5 5
 import JitsiLocalTrack from './JitsiLocalTrack.js';
6
-import JitsiRemoteTrack from './JitsiRemoteTrack.js';
7 6
 import JitsiTrackError from '../../JitsiTrackError';
8 7
 import * as JitsiTrackErrors from '../../JitsiTrackErrors';
9 8
 import Listenable from '../util/Listenable';
@@ -15,6 +14,8 @@ import VideoType from '../../service/RTC/VideoType';
15 14
 
16 15
 const logger = getLogger(__filename);
17 16
 
17
+let rtcTrackIdCounter = 0;
18
+
18 19
 /**
19 20
  *
20 21
  * @param tracksInfo
@@ -30,8 +31,10 @@ function createLocalTracks(tracksInfo, options) {
30 31
         } else if (trackInfo.videoType === VideoType.CAMERA) {
31 32
             deviceId = options.cameraDeviceId;
32 33
         }
34
+        rtcTrackIdCounter += 1;
33 35
         const localTrack
34 36
             = new JitsiLocalTrack(
37
+                rtcTrackIdCounter,
35 38
                 trackInfo.stream,
36 39
                 trackInfo.track,
37 40
                 trackInfo.mediaType,
@@ -73,8 +76,6 @@ export default class RTC extends Listenable {
73 76
 
74 77
         this.localTracks = [];
75 78
 
76
-        // FIXME: We should support multiple streams per jid.
77
-        this.remoteTracks = {};
78 79
         this.options = options;
79 80
 
80 81
         // A flag whether we had received that the data channel had opened
@@ -270,6 +271,8 @@ export default class RTC extends Listenable {
270 271
         return RTCUtils.getDeviceAvailability();
271 272
     }
272 273
 
274
+    /* eslint-disable max-params */
275
+
273 276
     /**
274 277
      * Creates new <tt>TraceablePeerConnection</tt>
275 278
      * @param {SignalingLayer} signaling the signaling layer that will
@@ -277,6 +280,8 @@ export default class RTC extends Listenable {
277 280
      * over SDP.
278 281
      * @param {Object} iceConfig an object describing the ICE config like
279 282
      * defined in the WebRTC specification.
283
+     * @param {boolean} isP2P indicates whether or not the new TPC will be used
284
+     * in a peer to peer type of session
280 285
      * @param {Object} options the config options
281 286
      * @param {boolean} options.disableSimulcast if set to 'true' will disable
282 287
      * the simulcast
@@ -285,12 +290,12 @@ export default class RTC extends Listenable {
285 290
      * preferred over other video codecs.
286 291
      * @return {TraceablePeerConnection}
287 292
      */
288
-    createPeerConnection(signaling, iceConfig, options) {
293
+    createPeerConnection(signaling, iceConfig, isP2P, options) {
289 294
         const newConnection
290 295
             = new TraceablePeerConnection(
291 296
                 this,
292 297
                 this.peerConnectionIdCounter,
293
-                signaling, iceConfig, RTC.getPCConstraints(), options);
298
+                signaling, iceConfig, RTC.getPCConstraints(), isP2P, options);
294 299
 
295 300
         this.peerConnections.set(newConnection.id, newConnection);
296 301
         this.peerConnectionIdCounter += 1;
@@ -298,6 +303,8 @@ export default class RTC extends Listenable {
298 303
         return newConnection;
299 304
     }
300 305
 
306
+    /* eslint-enable max-params */
307
+
301 308
     /**
302 309
      * Removed given peer connection from this RTC module instance.
303 310
      * @param {TraceablePeerConnection} traceablePeerConnection
@@ -379,64 +386,19 @@ export default class RTC extends Listenable {
379 386
      * @return {Array<JitsiRemoteTrack>}
380 387
      */
381 388
     getRemoteTracks(mediaType) {
382
-        const remoteTracks = [];
383
-        const remoteEndpoints = Object.keys(this.remoteTracks);
384
-
385
-        for (const endpoint of remoteEndpoints) {
386
-            const endpointMediaTypes = Object.keys(this.remoteTracks[endpoint]);
389
+        let remoteTracks = [];
387 390
 
388
-            for (const trackMediaType of endpointMediaTypes) {
389
-                // per media type filtering
390
-                if (!mediaType || mediaType === trackMediaType) {
391
-                    const mediaTrack
392
-                        = this.remoteTracks[endpoint][trackMediaType];
391
+        for (const tpc of this.peerConnections.values()) {
392
+            const pcRemoteTracks = tpc.getRemoteTracks(undefined, mediaType);
393 393
 
394
-                    if (mediaTrack) {
395
-                        remoteTracks.push(mediaTrack);
396
-                    }
397
-                }
394
+            if (pcRemoteTracks) {
395
+                remoteTracks = remoteTracks.concat(pcRemoteTracks);
398 396
             }
399 397
         }
400 398
 
401 399
         return remoteTracks;
402 400
     }
403 401
 
404
-    /**
405
-     * Gets JitsiRemoteTrack for the passed MediaType associated with given MUC
406
-     * nickname (resource part of the JID).
407
-     * @param type audio or video.
408
-     * @param resource the resource part of the MUC JID
409
-     * @returns {JitsiRemoteTrack|null}
410
-     */
411
-    getRemoteTrackByType(type, resource) {
412
-        if (this.remoteTracks[resource]) {
413
-            return this.remoteTracks[resource][type];
414
-        }
415
-
416
-        return null;
417
-
418
-    }
419
-
420
-    /**
421
-     * Gets JitsiRemoteTrack for AUDIO MediaType associated with given MUC
422
-     * nickname (resource part of the JID).
423
-     * @param resource the resource part of the MUC JID
424
-     * @returns {JitsiRemoteTrack|null}
425
-     */
426
-    getRemoteAudioTrack(resource) {
427
-        return this.getRemoteTrackByType(MediaType.AUDIO, resource);
428
-    }
429
-
430
-    /**
431
-     * Gets JitsiRemoteTrack for VIDEO MediaType associated with given MUC
432
-     * nickname (resource part of the JID).
433
-     * @param resource the resource part of the MUC JID
434
-     * @returns {JitsiRemoteTrack|null}
435
-     */
436
-    getRemoteVideoTrack(resource) {
437
-        return this.getRemoteTrackByType(MediaType.VIDEO, resource);
438
-    }
439
-
440 402
     /**
441 403
      * Set mute for all local audio streams attached to the conference.
442 404
      * @param value the mute value
@@ -469,56 +431,6 @@ export default class RTC extends Listenable {
469 431
         this.localTracks.splice(pos, 1);
470 432
     }
471 433
 
472
-    /* eslint-disable max-params */
473
-
474
-    /**
475
-     * Initializes a new JitsiRemoteTrack instance with the data provided by
476
-     * the signaling layer and SDP.
477
-     *
478
-     * @param {string} ownerEndpointId
479
-     * @param {MediaStream} stream
480
-     * @param {MediaStreamTrack} track
481
-     * @param {MediaType} mediaType
482
-     * @param {VideoType|undefined} videoType
483
-     * @param {string} ssrc
484
-     * @param {boolean} muted
485
-     */
486
-    _createRemoteTrack(
487
-            ownerEndpointId,
488
-            stream,
489
-            track,
490
-            mediaType,
491
-            videoType,
492
-            ssrc,
493
-            muted) {
494
-        const remoteTrack
495
-            = new JitsiRemoteTrack(
496
-                this,
497
-                this.conference,
498
-                ownerEndpointId,
499
-                stream,
500
-                track,
501
-                mediaType,
502
-                videoType,
503
-                ssrc,
504
-                muted);
505
-        const remoteTracks
506
-            = this.remoteTracks[ownerEndpointId]
507
-                || (this.remoteTracks[ownerEndpointId] = {});
508
-
509
-        if (remoteTracks[mediaType]) {
510
-            logger.error(
511
-                'Overwriting remote track!',
512
-                ownerEndpointId,
513
-                mediaType);
514
-        }
515
-        remoteTracks[mediaType] = remoteTrack;
516
-
517
-        this.eventEmitter.emit(RTCEvents.REMOTE_TRACK_ADDED, remoteTrack);
518
-    }
519
-
520
-    /* eslint-enable max-params */
521
-
522 434
     /**
523 435
      * Removes all JitsiRemoteTracks associated with given MUC nickname
524 436
      * (resource part of the JID). Returns array of removed tracks.
@@ -527,81 +439,19 @@ export default class RTC extends Listenable {
527 439
      * @returns {JitsiRemoteTrack[]}
528 440
      */
529 441
     removeRemoteTracks(owner) {
530
-        const removedTracks = [];
442
+        let removedTracks = [];
531 443
 
532
-        if (this.remoteTracks[owner]) {
533
-            const removedAudioTrack
534
-                = this.remoteTracks[owner][MediaType.AUDIO];
535
-            const removedVideoTrack
536
-                = this.remoteTracks[owner][MediaType.VIDEO];
444
+        for (const tpc of this.peerConnections.values()) {
445
+            const pcRemovedTracks = tpc.removeRemoteTracks(owner);
537 446
 
538
-            removedAudioTrack && removedTracks.push(removedAudioTrack);
539
-            removedVideoTrack && removedTracks.push(removedVideoTrack);
540
-
541
-            delete this.remoteTracks[owner];
447
+            removedTracks = removedTracks.concat(pcRemovedTracks);
542 448
         }
543 449
 
544
-        return removedTracks;
545
-    }
546
-
547
-    /**
548
-     * Finds remote track by it's stream and track ids.
549
-     * @param {string} streamId the media stream id as defined by the WebRTC
550
-     * @param {string} trackId the media track id as defined by the WebRTC
551
-     * @return {JitsiRemoteTrack|undefined}
552
-     * @private
553
-     */
554
-    _getRemoteTrackById(streamId, trackId) {
555
-        let result;
556
-
557
-        // .find will break the loop once the first match is found
558
-        Object.keys(this.remoteTracks).find(endpoint => {
559
-            const endpointTracks = this.remoteTracks[endpoint];
560
-
561
-            return endpointTracks && Object.keys(endpointTracks).find(
562
-                mediaType => {
563
-                    const mediaTrack = endpointTracks[mediaType];
564
-
565
-                    if (mediaTrack
566
-                        && mediaTrack.getStreamId() === streamId
567
-                        && mediaTrack.getTrackId() === trackId) {
568
-                        result = mediaTrack;
569
-
570
-                        return true;
571
-                    }
572
-
573
-                    return false;
574
-
575
-                });
576
-        });
577
-
578
-        return result;
579
-    }
580
-
581
-    /**
582
-     * Removes <tt>JitsiRemoteTrack</tt> identified by given stream and track
583
-     * ids.
584
-     *
585
-     * @param {string} streamId media stream id as defined by the WebRTC
586
-     * @param {string} trackId media track id as defined by the WebRTC
587
-     * @returns {JitsiRemoteTrack|undefined} the track which has been removed or
588
-     * <tt>undefined</tt> if no track matching given stream and track ids was
589
-     * found.
590
-     */
591
-    _removeRemoteTrack(streamId, trackId) {
592
-        const toBeRemoved = this._getRemoteTrackById(streamId, trackId);
593
-
594
-        if (toBeRemoved) {
595
-            toBeRemoved.dispose();
450
+        logger.debug(
451
+            `Removed remote tracks for ${owner}`
452
+                + ` count: ${removedTracks.length}`);
596 453
 
597
-            delete this.remoteTracks[
598
-                toBeRemoved.getParticipantId()][toBeRemoved.getType()];
599
-
600
-            this.eventEmitter.emit(
601
-                RTCEvents.REMOTE_TRACK_REMOVED, toBeRemoved);
602
-        }
603
-
604
-        return toBeRemoved;
454
+        return removedTracks;
605 455
     }
606 456
 
607 457
     /**
@@ -762,15 +612,19 @@ export default class RTC extends Listenable {
762 612
      * @param resource
763 613
      * @param audioLevel
764 614
      */
765
-    setAudioLevel(resource, audioLevel) {
766
-        if (!resource) {
615
+    setAudioLevel(ssrc, audioLevel) {
616
+        const track = this._getTrackBySSRC(ssrc);
617
+
618
+        if (!track) {
767 619
             return;
768 620
         }
769
-        const audioTrack = this.getRemoteAudioTrack(resource);
621
+        if (!track.isAudioTrack()) {
622
+            logger.warn(`Received audio level for non-audio track: ${ssrc}`);
770 623
 
771
-        if (audioTrack) {
772
-            audioTrack.setAudioLevel(audioLevel);
624
+            return;
773 625
         }
626
+
627
+        track.setAudioLevel(audioLevel);
774 628
     }
775 629
 
776 630
     /**
@@ -779,20 +633,36 @@ export default class RTC extends Listenable {
779 633
      * @param ssrc the ssrc to check.
780 634
      */
781 635
     getResourceBySSRC(ssrc) {
636
+        const track = this._getTrackBySSRC(ssrc);
782 637
 
783
-        // FIXME: Convert the SSRCs in whole project to use the same type.
784
-        // Now we are using number and string.
785
-        if (this.getLocalTracks().find(
638
+        return track ? track.getParticipantId() : null;
639
+    }
786 640
 
787
-            // eslint-disable-next-line eqeqeq
788
-                localTrack => localTrack.getSSRC() == ssrc)) {
789
-            return this.conference.myUserId();
790
-        }
641
+    /**
642
+     * Finds a track (either local or remote) which runs on the given SSRC.
643
+     * @param {string|number} ssrc
644
+     * @return {JitsiTrack|undefined}
645
+     *
646
+     * FIXME figure out where SSRC is stored as a string and convert to number
647
+     * @private
648
+     */
649
+    _getTrackBySSRC(ssrc) {
650
+        let track
651
+            = this.getLocalTracks().find(
652
+                localTrack =>
791 653
 
792
-        const track = this.getRemoteTrackBySSRC(ssrc);
654
+                    // It is important that SSRC is not compared with ===,
655
+                    // because the code calling this method is inconsistent
656
+                    // about string vs number types
657
+                    Array.from(this.peerConnections.values())
658
+                         .find(pc => pc.getLocalSSRC(localTrack) == ssrc) // eslint-disable-line eqeqeq, max-len
659
+                );
793 660
 
661
+        if (!track) {
662
+            track = this._getRemoteTrackBySSRC(ssrc);
663
+        }
794 664
 
795
-        return track ? track.getParticipantId() : null;
665
+        return track;
796 666
     }
797 667
 
798 668
     /**
@@ -801,40 +671,16 @@ export default class RTC extends Listenable {
801 671
      * @param ssrc the ssrc to check.
802 672
      * @return {JitsiRemoteTrack|undefined} return the first remote track that
803 673
      * matches given SSRC or <tt>undefined</tt> if no such track was found.
674
+     * @private
804 675
      */
805
-    getRemoteTrackBySSRC(ssrc) {
806
-
676
+    _getRemoteTrackBySSRC(ssrc) {
677
+        /* eslint-disable eqeqeq */
807 678
         // FIXME: Convert the SSRCs in whole project to use the same type.
808 679
         // Now we are using number and string.
809
-        // eslint-disable-next-line eqeqeq
810
-        return this.getRemoteTracks().find(t => ssrc == t.getSSRC());
811
-    }
812
-
813
-    /**
814
-     * Handles remote track mute / unmute events.
815
-     * @param type {string} "audio" or "video"
816
-     * @param isMuted {boolean} the new mute state
817
-     * @param from {string} user id
818
-     */
819
-    handleRemoteTrackMute(type, isMuted, from) {
820
-        const track = this.getRemoteTrackByType(type, from);
680
+        return this.getRemoteTracks().find(
681
+            remoteTrack => ssrc == remoteTrack.getSSRC());
821 682
 
822
-        if (track) {
823
-            track.setMute(isMuted);
824
-        }
825
-    }
826
-
827
-    /**
828
-     * Handles remote track video type events
829
-     * @param value {string} the new video type
830
-     * @param from {string} user id
831
-     */
832
-    handleRemoteTrackVideoTypeChanged(value, from) {
833
-        const videoTrack = this.getRemoteVideoTrack(from);
834
-
835
-        if (videoTrack) {
836
-            videoTrack._setVideoType(value);
837
-        }
683
+        /* eslint-enable eqeqeq */
838 684
     }
839 685
 
840 686
     /**

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

@@ -24,6 +24,17 @@ const RTCBrowserType = {
24 24
 
25 25
     RTC_BROWSER_REACT_NATIVE: 'rtc_browser.react-native',
26 26
 
27
+    /**
28
+     * Tells whether or not the <tt>MediaStream/tt> is removed from
29
+     * the <tt>PeerConnection</tt> and disposed on video mute (in order to turn
30
+     * off the camera device).
31
+     * @return {boolean} <tt>true</tt> if the current browser supports this
32
+     * strategy or <tt>false</tt> otherwise.
33
+     */
34
+    doesVideoMuteByStreamRemove() {
35
+        return !RTCBrowserType.isFirefox();
36
+    },
37
+
27 38
     /**
28 39
      * Gets current browser type.
29 40
      * @returns {string}
@@ -102,6 +113,15 @@ const RTCBrowserType = {
102 113
         return currentBrowser === RTCBrowserType.RTC_BROWSER_ELECTRON;
103 114
     },
104 115
 
116
+    /**
117
+     * Check whether or not the current browser support peer to peer connections
118
+     * @return {boolean} <tt>true</tt> if p2p is supported or <tt>false</tt>
119
+     * otherwise.
120
+     */
121
+    isP2PSupported() {
122
+        return !RTCBrowserType.isFirefox() && !RTCBrowserType.isReactNative();
123
+    },
124
+
105 125
     /**
106 126
      * Checks if current environment is React Native.
107 127
      * @returns {boolean}

+ 967
- 151
modules/RTC/TraceablePeerConnection.js
文件差异内容过多而无法显示
查看文件


+ 2
- 2
modules/connectivity/ConnectionQuality.js 查看文件

@@ -214,8 +214,8 @@ export default class ConnectionQuality {
214 214
 
215 215
         conference.room.addListener(
216 216
             XMPPEvents.ICE_CONNECTION_STATE_CHANGED,
217
-            newState => {
218
-                if (newState === 'connected') {
217
+            (jingleSession, newState) => {
218
+                if (!jingleSession.isP2P && newState === 'connected') {
219 219
                     this._timeIceConnected = window.performance.now();
220 220
                 }
221 221
             });

+ 30
- 5
modules/connectivity/ParticipantConnectionStatus.js 查看文件

@@ -101,6 +101,10 @@ export default class ParticipantConnectionStatus {
101 101
             RTCEvents.ENDPOINT_CONN_STATUS_CHANGED,
102 102
             this._onEndpointConnStatusChanged);
103 103
 
104
+        // Handles P2P status changes
105
+        this._onP2PStatus = this.refreshConnectionStatusForAll.bind(this);
106
+        this.conference.on(JitsiConferenceEvents.P2P_STATUS, this._onP2PStatus);
107
+
104 108
         // On some browsers MediaStreamTrack trigger "onmute"/"onunmute"
105 109
         // events for video type tracks when they stop receiving data which is
106 110
         // often a sign that remote user is having connectivity issues
@@ -159,10 +163,15 @@ export default class ParticipantConnectionStatus {
159 163
                 this._onRemoteTrackRemoved);
160 164
         }
161 165
 
162
-        Object.keys(this.trackTimers).forEach(participantId => {
166
+        this.conference.off(
167
+            JitsiConferenceEvents.P2P_STATUS, this._onP2PStatus);
168
+
169
+        const participantIds = Object.keys(this.trackTimers);
170
+
171
+        for (const participantId of participantIds) {
163 172
             this.clearTimeout(participantId);
164 173
             this.clearRtcMutedTimestamp(participantId);
165
-        });
174
+        }
166 175
 
167 176
         // Clear RTC connection status cache
168 177
         this.connStatusFromJvb = {};
@@ -321,6 +330,19 @@ export default class ParticipantConnectionStatus {
321 330
             && (Date.now() - rtcMutedTimestamp) >= this.rtcMuteTimeout;
322 331
     }
323 332
 
333
+    /**
334
+     * Goes over every participant and updates connectivity status.
335
+     * Should be called when a parameter which affects all of the participants
336
+     * is changed (P2P for example).
337
+     */
338
+    refreshConnectionStatusForAll() {
339
+        const participants = this.conference.getParticipants();
340
+
341
+        for (const participant of participants) {
342
+            this.figureOutConnectionStatus(participant.getId());
343
+        }
344
+    }
345
+
324 346
     /**
325 347
      * Figures out (and updates) the current connectivity status for
326 348
      * the participant identified by the given id.
@@ -348,9 +370,12 @@ export default class ParticipantConnectionStatus {
348 370
         const isInLastN = this.rtc.isInLastN(id);
349 371
         let isConnActiveByJvb = this.connStatusFromJvb[id];
350 372
 
351
-        // If no status was received from the JVB it means that it's active
352
-        // (the bridge does not send notification unless there is a problem).
353
-        if (typeof isConnActiveByJvb !== 'boolean') {
373
+        if (this.conference.isP2PActive()) {
374
+            logger.debug('Assuming connection active by JVB - in P2P mode');
375
+            isConnActiveByJvb = true;
376
+        } else if (typeof isConnActiveByJvb !== 'boolean') {
377
+            // If no status was received from the JVB it means that it's active
378
+            // (the bridge does not send notification unless there is a problem)
354 379
             logger.debug('Assuming connection active by JVB - no notification');
355 380
             isConnActiveByJvb = true;
356 381
         }

+ 12
- 7
modules/statistics/RTPStatsCollector.js 查看文件

@@ -473,12 +473,15 @@ StatsCollector.prototype.processStatsReport = function() {
473 473
 
474 474
             if (!conferenceStatsTransport.some(
475 475
                     t =>
476
-                        t.ip === ip
477
-                        && t.type === type
478
-                        && t.localip === localip)) {
479
-                conferenceStatsTransport.push({ ip,
476
+                       t.ip === ip
477
+                       && t.type === type
478
+                       && t.localip === localip)) {
479
+                conferenceStatsTransport.push({
480
+                    ip,
480 481
                     type,
481
-                    localip });
482
+                    localip,
483
+                    p2p: this.peerconnection.isP2P
484
+                });
482 485
             }
483 486
             continue;
484 487
         }
@@ -495,7 +498,8 @@ StatsCollector.prototype.processStatsReport = function() {
495 498
             this.conferenceStats.transport.push({
496 499
                 ip: `${remote.ipAddress}:${remote.portNumber}`,
497 500
                 type: local.transport,
498
-                localip: `${local.ipAddress}:${local.portNumber}`
501
+                localip: `${local.ipAddress}:${local.portNumber}`,
502
+                p2p: this.peerconnection.isP2P
499 503
             });
500 504
         }
501 505
 
@@ -661,7 +665,8 @@ StatsCollector.prototype.processStatsReport = function() {
661 665
         this
662 666
     );
663 667
 
664
-    this.eventEmitter.emit(StatisticsEvents.BYTE_SENT_STATS, byteSentStats);
668
+    this.eventEmitter.emit(
669
+        StatisticsEvents.BYTE_SENT_STATS, this.peerconnection, byteSentStats);
665 670
 
666 671
     this.conferenceStats.bitrate
667 672
       = { 'upload': bitrateUpload,

+ 32
- 8
modules/xmpp/ChatRoom.js 查看文件

@@ -534,14 +534,16 @@ export default class ChatRoom extends Listenable {
534 534
         // make sure we catch all errors coming from any handler
535 535
         // otherwise we can remove the presence handler from strophe
536 536
         try {
537
-            let tagHandler = this.presHandlers[node.tagName];
537
+            let tagHandlers = this.presHandlers[node.tagName];
538 538
 
539 539
             if (node.tagName.startsWith('jitsi_participant_')) {
540
-                tagHandler = this.participantPropertyListener;
540
+                tagHandlers = [ this.participantPropertyListener ];
541 541
             }
542 542
 
543
-            if (tagHandler) {
544
-                tagHandler(node, Strophe.getResourceFromJid(from), from);
543
+            if (tagHandlers) {
544
+                tagHandlers.forEach(handler => {
545
+                    handler(node, Strophe.getResourceFromJid(from), from);
546
+                });
545 547
             }
546 548
         } catch (e) {
547 549
             GlobalOnErrorHandler.callErrorHandler(e);
@@ -890,15 +892,37 @@ export default class ChatRoom extends Listenable {
890 892
      * @param handler
891 893
      */
892 894
     addPresenceListener(name, handler) {
893
-        this.presHandlers[name] = handler;
895
+        if (typeof handler !== 'function') {
896
+            throw new Error('"handler" is not a function');
897
+        }
898
+        let tagHandlers = this.presHandlers[name];
899
+
900
+        if (!tagHandlers) {
901
+            this.presHandlers[name] = tagHandlers = [];
902
+        }
903
+        if (tagHandlers.indexOf(handler) === -1) {
904
+            tagHandlers.push(handler);
905
+        } else {
906
+            logger.warn(
907
+                `Trying to add the same handler more than once for: ${name}`);
908
+        }
894 909
     }
895 910
 
896 911
     /**
897 912
      *
898 913
      * @param name
914
+     * @param handler
899 915
      */
900
-    removePresenceListener(name) {
901
-        delete this.presHandlers[name];
916
+    removePresenceListener(name, handler) {
917
+        const tagHandlers = this.presHandlers[name];
918
+        const handlerIdx = tagHandlers ? tagHandlers.indexOf(handler) : -1;
919
+
920
+        // eslint-disable-next-line no-negated-condition
921
+        if (handlerIdx !== -1) {
922
+            tagHandlers.splice(handlerIdx, 1);
923
+        } else {
924
+            logger.warn(`Handler for: ${name} was not registered`);
925
+        }
902 926
     }
903 927
 
904 928
     /**
@@ -953,7 +977,7 @@ export default class ChatRoom extends Listenable {
953 977
     /**
954 978
      *
955 979
      * @param mute
956
-     * @apram callback
980
+     * @param callback
957 981
      */
958 982
     setAudioMute(mute, callback) {
959 983
         return this.sendAudioInfoPresence(mute, callback);

+ 676
- 629
modules/xmpp/JingleSessionPC.js
文件差异内容过多而无法显示
查看文件


+ 19
- 4
modules/xmpp/RtxModifier.js 查看文件

@@ -120,17 +120,30 @@ export default class RtxModifier {
120 120
 
121 121
             return sdpStr;
122 122
         }
123
+
124
+        return this.modifyRtxSsrcs2(videoMLine)
125
+            ? sdpTransformer.toRawSDP() : sdpStr;
126
+    }
127
+
128
+    /**
129
+     * Does the same thing as {@link modifyRtxSsrcs}, but takes the
130
+     *  {@link MLineWrap} instance wrapping video media as an argument.
131
+     * @param {MLineWrap} videoMLine
132
+     * @return {boolean} <tt>true</tt> if the SDP wrapped by
133
+     *  {@link SdpTransformWrap} has been modified or <tt>false</tt> otherwise.
134
+     */
135
+    modifyRtxSsrcs2(videoMLine) {
123 136
         if (videoMLine.direction === 'inactive'
124
-                || videoMLine.direction === 'recvonly') {
137
+            || videoMLine.direction === 'recvonly') {
125 138
             logger.debug('RtxModifier doing nothing, video '
126 139
                 + 'm line is inactive or recvonly');
127 140
 
128
-            return sdpStr;
141
+            return false;
129 142
         }
130 143
         if (videoMLine.getSSRCCount() < 1) {
131 144
             logger.debug('RtxModifier doing nothing, no video ssrcs present');
132 145
 
133
-            return sdpStr;
146
+            return false;
134 147
         }
135 148
         logger.debug('Current ssrc mapping: ', this.correspondingRtxSsrcs);
136 149
         const primaryVideoSsrcs = videoMLine.getPrimaryVideoSSRCs();
@@ -179,7 +192,9 @@ export default class RtxModifier {
179 192
                 correspondingRtxSsrc);
180 193
         }
181 194
 
182
-        return sdpTransformer.toRawSDP();
195
+        // FIXME we're not looking into much details whether the SDP has been
196
+        // modified or not once the precondition requirements are met.
197
+        return true;
183 198
     }
184 199
 
185 200
     /**

+ 1
- 2
modules/xmpp/SDPDiffer.js 查看文件

@@ -92,8 +92,7 @@ SDPDiffer.prototype.getNewMedia = function() {
92 92
                 const mySsrcGroup = myMedia.ssrcGroups[i];
93 93
 
94 94
                 if (otherSsrcGroup.semantics === mySsrcGroup.semantics
95
-                    && arrayEquals(otherSsrcGroup.ssrcs,
96
-                                      [ mySsrcGroup.ssrcs ])) {
95
+                    && arrayEquals(otherSsrcGroup.ssrcs, mySsrcGroup.ssrcs)) {
97 96
 
98 97
                     matched = true;
99 98
                     break;

+ 14
- 0
modules/xmpp/SDPUtil.js 查看文件

@@ -546,6 +546,20 @@ const SDPUtil = {
546 546
         return sdp.media.find(m => m.type === type);
547 547
     },
548 548
 
549
+    /**
550
+     * Extracts the ICE username fragment from an SDP string.
551
+     * @param {string} sdp the SDP in raw text format
552
+     */
553
+    getUfrag(sdp) {
554
+        const ufragLines
555
+            = sdp.split('\n').filter(
556
+                    line => line.startsWith('a=ice-ufrag:'));
557
+
558
+        if (ufragLines.length > 0) {
559
+            return ufragLines[0].substr('a=ice-ufrag:'.length);
560
+        }
561
+    },
562
+
549 563
     /**
550 564
      * Sets the given codecName as the preferred codec by
551 565
      *  moving it to the beginning of the payload types

+ 57
- 3
modules/xmpp/SdpConsistency.js 查看文件

@@ -21,15 +21,21 @@ export default class SdpConsistency {
21 21
      * Constructor
22 22
      */
23 23
     constructor() {
24
-        this.clearSsrcCache();
24
+        this.clearVideoSsrcCache();
25
+
26
+        /**
27
+         * Cached audio SSRC.
28
+         * @type {number|null}
29
+         */
30
+        this.cachedAudioSSRC = null;
25 31
     }
26 32
 
27 33
     /**
28
-     * Clear the cached primary and primary rtx ssrcs so that
34
+     * Clear the cached video primary and primary rtx ssrcs so that
29 35
      *  they will not be used for the next call to
30 36
      *  makeVideoPrimarySsrcsConsistent
31 37
      */
32
-    clearSsrcCache() {
38
+    clearVideoSsrcCache() {
33 39
         this.cachedPrimarySsrc = null;
34 40
     }
35 41
 
@@ -120,4 +126,52 @@ export default class SdpConsistency {
120 126
 
121 127
         return sdpTransformer.toRawSDP();
122 128
     }
129
+
130
+    /**
131
+     * Makes sure that audio SSRC is preserved between "detach" and "attach"
132
+     *  operations. The code assumes there can be only 1 audio track added to
133
+     *  the peer connection at a time.
134
+     * @param {string} sdpStr the sdp string to (potentially)
135
+     *  change to make the audio ssrc consistent
136
+     * @returns {string} a (potentially) modified sdp string
137
+     *  with ssrcs consistent with this class' cache
138
+     */
139
+    makeAudioSSRCConsistent(sdpStr) {
140
+        const sdpTransformer = new SdpTransformWrap(sdpStr);
141
+        const audioMLine = sdpTransformer.selectMedia('audio');
142
+
143
+        if (!audioMLine) {
144
+            logger.error(`No 'audio' media found in the sdp: ${sdpStr}`);
145
+
146
+            return sdpStr;
147
+        }
148
+        if (audioMLine.direction === 'inactive') {
149
+            logger.info(
150
+                'Sdp-consistency doing nothing, audio mline is inactive');
151
+
152
+            return sdpStr;
153
+        }
154
+
155
+        const audioSSRCObj = audioMLine.findSSRCByMSID(null);
156
+
157
+        if (audioSSRCObj) {
158
+            if (this.cachedAudioSSRC) {
159
+                const oldSSRC = audioSSRCObj.id;
160
+
161
+                if (oldSSRC !== this.cachedAudioSSRC) {
162
+                    logger.info(
163
+                        `Replacing audio SSRC ${
164
+                            oldSSRC} with ${this.cachedAudioSSRC}`);
165
+                    audioMLine.replaceSSRC(oldSSRC, this.cachedAudioSSRC);
166
+                }
167
+            } else {
168
+                this.cachedAudioSSRC = audioSSRCObj.id;
169
+                logger.info(`Storing audio SSRC: ${this.cachedAudioSSRC}`);
170
+            }
171
+        } else {
172
+            logger.info('Doing nothing - no audio stream in the SDP');
173
+        }
174
+
175
+        return sdpTransformer.toRawSDP();
176
+    }
123 177
 }

+ 31
- 5
modules/xmpp/SdpTransformUtil.js 查看文件

@@ -54,9 +54,13 @@ class MLineWrap {
54 54
     }
55 55
 
56 56
     /**
57
+     * Getter for the mLine's "ssrcs" array. If the array was undefined an empty
58
+     * one will be preassigned.
57 59
      *
60
+     * @return {Array<Object>} an array of 'sdp-transform' SSRC attributes
61
+     * objects.
58 62
      */
59
-    get _ssrcs() {
63
+    get ssrcs() {
60 64
         if (!this.mLine.ssrcs) {
61 65
             this.mLine.ssrcs = [];
62 66
         }
@@ -64,6 +68,16 @@ class MLineWrap {
64 68
         return this.mLine.ssrcs;
65 69
     }
66 70
 
71
+    /**
72
+     * Setter for the mLine's "ssrcs" array.
73
+     *
74
+     * @param {Array<Object>} ssrcs an array of 'sdp-transform' SSRC attributes
75
+     * objects.
76
+     */
77
+    set ssrcs(ssrcs) {
78
+        this.mLine.ssrcs = ssrcs;
79
+    }
80
+
67 81
     /**
68 82
      * Returns the direction of the underlying media description.
69 83
      * @return {string} the media direction name as defined in the SDP.
@@ -110,7 +124,7 @@ class MLineWrap {
110 124
      * <tt>undefined</tt> if no such attribute exists.
111 125
      */
112 126
     getSSRCAttrValue(ssrcNumber, attrName) {
113
-        const attribute = this._ssrcs.find(
127
+        const attribute = this.ssrcs.find(
114 128
             ssrcObj => ssrcObj.id === ssrcNumber
115 129
             && ssrcObj.attribute === attrName);
116 130
 
@@ -138,7 +152,7 @@ class MLineWrap {
138 152
      * the 'sdp-transform' lib.
139 153
      */
140 154
     addSSRCAttribute(ssrcObj) {
141
-        this._ssrcs.push(ssrcObj);
155
+        this.ssrcs.push(ssrcObj);
142 156
     }
143 157
 
144 158
     /**
@@ -179,6 +193,18 @@ class MLineWrap {
179 193
                 && parsePrimarySSRC(group) === primarySSRC);
180 194
     }
181 195
 
196
+    /**
197
+     * @param {string|null} msid the media stream id or <tt>null</tt> to match
198
+     * the first SSRC object with any 'msid' value.
199
+     * @return {Object|undefined} the SSRC object as defined by 'sdp-transform'
200
+     * lib.
201
+     */
202
+    findSSRCByMSID(msid) {
203
+        return this.ssrcs.find(
204
+            ssrcObj => ssrcObj.attribute === 'msid'
205
+                && (msid === null || ssrcObj.value === msid));
206
+    }
207
+
182 208
     /**
183 209
      * Gets the SSRC count for the underlying media description.
184 210
      * @return {number}
@@ -212,7 +238,7 @@ class MLineWrap {
212 238
         const numSsrcs = _getSSRCCount(this.mLine);
213 239
 
214 240
         if (numSsrcs === 1) {
215
-            // Not using _ssrcs on purpose here
241
+            // Not using "ssrcs" getter on purpose here
216 242
             return this.mLine.ssrcs[0].id;
217 243
         }
218 244
 
@@ -252,7 +278,7 @@ class MLineWrap {
252 278
      * @return {Array.<number>} an array with all SSRC as numbers.
253 279
      */
254 280
     getSSRCs() {
255
-        return this._ssrcs
281
+        return this.ssrcs
256 282
             .map(ssrcInfo => ssrcInfo.id)
257 283
             .filter((ssrc, index, array) => array.indexOf(ssrc) === index);
258 284
     }

+ 104
- 0
modules/xmpp/SignalingLayerImpl.js 查看文件

@@ -0,0 +1,104 @@
1
+/* global __filename */
2
+
3
+import { getLogger } from 'jitsi-meet-logger';
4
+import * as MediaType from '../../service/RTC/MediaType';
5
+import * as SignalingEvents from '../../service/RTC/SignalingEvents';
6
+import SignalingLayer from '../../service/RTC/SignalingLayer';
7
+
8
+const logger = getLogger(__filename);
9
+
10
+/**
11
+ * Default XMPP implementation of the {@link SignalingLayer} interface. Obtains
12
+ * the data from the MUC presence.
13
+ */
14
+export default class SignalingLayerImpl extends SignalingLayer {
15
+    /**
16
+     * Creates new instance.
17
+     */
18
+    constructor() {
19
+        super();
20
+
21
+        /**
22
+         * A map that stores SSRCs of remote streams. And is used only locally
23
+         * We store the mapping when jingle is received, and later is used
24
+         * onaddstream webrtc event where we have only the ssrc
25
+         * FIXME: This map got filled and never cleaned and can grow during long
26
+         * conference
27
+         * @type {Map<string, string>} maps SSRC number to jid
28
+         */
29
+        this.ssrcOwners = new Map();
30
+
31
+        /**
32
+         *
33
+         * @type {ChatRoom|null}
34
+         */
35
+        this.chatRoom = null;
36
+    }
37
+
38
+    /**
39
+     * Sets the <tt>ChatRoom</tt> instance used and binds presence listeners.
40
+     * @param {ChatRoom} room
41
+     */
42
+    setChatRoom(room) {
43
+        const oldChatRoom = this.chatRoom;
44
+
45
+        this.chatRoom = room;
46
+        if (oldChatRoom) {
47
+            oldChatRoom.removePresenceListener(
48
+                'audiomuted', this._audioMuteHandler);
49
+            oldChatRoom.removePresenceListener(
50
+                'videomuted', this._videoMuteHandler);
51
+            oldChatRoom.removePresenceListener(
52
+                'videoType', this._videoTypeHandler);
53
+        }
54
+        if (room) {
55
+            // SignalingEvents
56
+            this._audioMuteHandler = (node, from) => {
57
+                this.eventEmitter.emit(
58
+                    SignalingEvents.PEER_MUTED_CHANGED,
59
+                    from, MediaType.AUDIO, node.value === 'true');
60
+            };
61
+            room.addPresenceListener('audiomuted', this._audioMuteHandler);
62
+
63
+            this._videoMuteHandler = (node, from) => {
64
+                this.eventEmitter.emit(
65
+                    SignalingEvents.PEER_MUTED_CHANGED,
66
+                    from, MediaType.VIDEO, node.value === 'true');
67
+            };
68
+            room.addPresenceListener('videomuted', this._videoMuteHandler);
69
+
70
+            this._videoTypeHandler = (node, from) => {
71
+                this.eventEmitter.emit(
72
+                    SignalingEvents.PEER_VIDEO_TYPE_CHANGED,
73
+                    from, node.value);
74
+            };
75
+            room.addPresenceListener('videoType', this._videoTypeHandler);
76
+        }
77
+    }
78
+
79
+    /**
80
+     * @inheritDoc
81
+     */
82
+    getPeerMediaInfo(owner, mediaType) {
83
+        if (this.chatRoom) {
84
+            return this.chatRoom.getMediaPresenceInfo(owner, mediaType);
85
+        }
86
+        logger.error('Requested peer media info, before room was set');
87
+    }
88
+
89
+    /**
90
+     * @inheritDoc
91
+     */
92
+    getSSRCOwner(ssrc) {
93
+        return this.ssrcOwners.get(ssrc);
94
+    }
95
+
96
+    /**
97
+     * Set an SSRC owner.
98
+     * @param {string} ssrc an SSRC to be owned
99
+     * @param {string} endpointId owner's ID (MUC nickname)
100
+     */
101
+    setSSRCOwner(ssrc, endpointId) {
102
+        this.ssrcOwners.set(ssrc, endpointId);
103
+    }
104
+}

+ 70
- 12
modules/xmpp/strophe.jingle.js 查看文件

@@ -1,11 +1,13 @@
1
-/* global $, $iq, Strophe */
1
+/* global $, $iq, __filename, Strophe */
2 2
 
3 3
 import { getLogger } from 'jitsi-meet-logger';
4 4
 const logger = getLogger(__filename);
5 5
 
6 6
 import JingleSessionPC from './JingleSessionPC';
7
+import * as JingleSessionState from './JingleSessionState';
7 8
 import XMPPEvents from '../../service/xmpp/XMPPEvents';
8 9
 import GlobalOnErrorHandler from '../util/GlobalOnErrorHandler';
10
+import RandomUtil from '../util/RandomUtil';
9 11
 import Statistics from '../statistics/statistics';
10 12
 import ConnectionPlugin from './ConnectionPlugin';
11 13
 
@@ -18,16 +20,24 @@ import ConnectionPlugin from './ConnectionPlugin';
18 20
  */
19 21
 class JingleConnectionPlugin extends ConnectionPlugin {
20 22
     /**
21
-     *
22
-     * @param xmpp
23
-     * @param eventEmitter
23
+     * Creates new <tt>JingleConnectionPlugin</tt>
24
+     * @param {XMPP} xmpp
25
+     * @param {EventEmitter} eventEmitter
26
+     * @param {Array<Object>} p2pStunServers an array which is part of the ice
27
+     * config passed to the <tt>PeerConnection</tt> with the structure defined
28
+     * by the WebRTC standard.
24 29
      */
25
-    constructor(xmpp, eventEmitter) {
30
+    constructor(xmpp, eventEmitter, p2pStunServers) {
26 31
         super();
27 32
         this.xmpp = xmpp;
28 33
         this.eventEmitter = eventEmitter;
29 34
         this.sessions = {};
30
-        this.iceConfig = { iceServers: [] };
35
+        this.jvbIceConfig = { iceServers: [ ] };
36
+        this.p2pIceConfig = { iceServers: [ ] };
37
+        if (Array.isArray(p2pStunServers)) {
38
+            logger.info('Configured STUN servers: ', p2pStunServers);
39
+            this.p2pIceConfig.iceServers = p2pStunServers;
40
+        }
31 41
         this.mediaConstraints = {
32 42
             mandatory: {
33 43
                 'OfferToReceiveAudio': true,
@@ -128,15 +138,27 @@ class JingleConnectionPlugin extends ConnectionPlugin {
128 138
                 const videoMuted = startMuted.attr('video');
129 139
 
130 140
                 this.eventEmitter.emit(XMPPEvents.START_MUTED_FROM_FOCUS,
131
-                            audioMuted === 'true', videoMuted === 'true');
141
+                        audioMuted === 'true', videoMuted === 'true');
132 142
             }
143
+
144
+            // FIXME that should work most of the time, but we'd have to
145
+            // think how secure it is to assume that user with "focus"
146
+            // nickname is Jicofo.
147
+            const isP2P = Strophe.getResourceFromJid(fromJid) !== 'focus';
148
+
149
+            logger.info(
150
+                `Marking session from ${fromJid
151
+                } as ${isP2P ? '' : '*not*'} P2P`);
133 152
             sess = new JingleSessionPC(
134 153
                         $(iq).find('jingle').attr('sid'),
135 154
                         $(iq).attr('to'),
136 155
                         fromJid,
137 156
                         this.connection,
138 157
                         this.mediaConstraints,
139
-                        this.iceConfig, this.xmpp.options);
158
+                        isP2P ? this.p2pIceConfig : this.jvbIceConfig,
159
+                        isP2P /* P2P */,
160
+                        false /* initiator */,
161
+                        this.xmpp.options);
140 162
 
141 163
             this.sessions[sess.sid] = sess;
142 164
 
@@ -146,6 +168,16 @@ class JingleConnectionPlugin extends ConnectionPlugin {
146 168
                     'xmpp.session-initiate', { value: now });
147 169
             break;
148 170
         }
171
+        case 'session-accept': {
172
+            this.eventEmitter.emit(
173
+                XMPPEvents.CALL_ACCEPTED, sess, $(iq).find('>jingle'));
174
+            break;
175
+        }
176
+        case 'transport-info': {
177
+            this.eventEmitter.emit(
178
+                XMPPEvents.TRANSPORT_INFO, sess, $(iq).find('>jingle'));
179
+            break;
180
+        }
149 181
         case 'session-terminate': {
150 182
             logger.log('terminating...', sess.sid);
151 183
             let reasonCondition = null;
@@ -156,9 +188,10 @@ class JingleConnectionPlugin extends ConnectionPlugin {
156 188
                     = $(iq).find('>jingle>reason>:first')[0].tagName;
157 189
                 reasonText = $(iq).find('>jingle>reason>text').text();
158 190
             }
191
+            sess.state = JingleSessionState.ENDED;
159 192
             this.terminate(sess.sid, reasonCondition, reasonText);
160 193
             this.eventEmitter.emit(XMPPEvents.CALL_ENDED,
161
-                    sess, reasonCondition, reasonText);
194
+                sess, reasonCondition, reasonText);
162 195
             break;
163 196
         }
164 197
         case 'transport-replace':
@@ -202,6 +235,31 @@ class JingleConnectionPlugin extends ConnectionPlugin {
202 235
         return true;
203 236
     }
204 237
 
238
+    /**
239
+     * Creates new <tt>JingleSessionPC</tt> meant to be used in a direct P2P
240
+     * connection, configured as 'initiator'.
241
+     * @param {string} me our JID
242
+     * @param {string} peer remote participant's JID
243
+     * @return {JingleSessionPC}
244
+     */
245
+    newP2PJingleSession(me, peer) {
246
+        const sess
247
+            = new JingleSessionPC(
248
+                    RandomUtil.randomHexString(12),
249
+                    me,
250
+                    peer,
251
+                    this.connection,
252
+                    this.mediaConstraints,
253
+                    this.p2pIceConfig,
254
+                    true /* P2P */,
255
+                    true /* initiator */,
256
+                    this.xmpp.options);
257
+
258
+        this.sessions[sess.sid] = sess;
259
+
260
+        return sess;
261
+    }
262
+
205 263
     /**
206 264
      *
207 265
      * @param sid
@@ -296,7 +354,7 @@ class JingleConnectionPlugin extends ConnectionPlugin {
296 354
                     }
297 355
                     }
298 356
                 });
299
-                this.iceConfig.iceServers = iceservers;
357
+                this.jvbIceConfig.iceServers = iceservers;
300 358
             }, err => {
301 359
                 logger.warn('getting turn credentials failed', err);
302 360
                 logger.warn('is mod_turncredentials or similar installed?');
@@ -331,8 +389,8 @@ class JingleConnectionPlugin extends ConnectionPlugin {
331 389
 
332 390
 /* eslint-enable newline-per-chained-call */
333 391
 
334
-module.exports = function(XMPP, eventEmitter) {
392
+module.exports = function(XMPP, eventEmitter, p2pStunServers) {
335 393
     Strophe.addConnectionPlugin(
336 394
         'jingle',
337
-        new JingleConnectionPlugin(XMPP, eventEmitter));
395
+        new JingleConnectionPlugin(XMPP, eventEmitter, p2pStunServers));
338 396
 };

+ 5
- 3
modules/xmpp/xmpp.js 查看文件

@@ -36,8 +36,10 @@ function createConnection(token, bosh = '/http-bind') {
36 36
  */
37 37
 export default class XMPP extends Listenable {
38 38
     /**
39
-     *
40
-     * @param options
39
+     * FIXME describe all options
40
+     * @param {Object} options
41
+     * @param {Array<Object>} options.p2pStunServers see
42
+     * {@link JingleConnectionPlugin} for more details.
41 43
      * @param token
42 44
      */
43 45
     constructor(options, token) {
@@ -442,7 +444,7 @@ export default class XMPP extends Listenable {
442 444
      */
443 445
     _initStrophePlugins() {
444 446
         initEmuc(this);
445
-        initJingle(this, this.eventEmitter);
447
+        initJingle(this, this.eventEmitter, this.options.p2pStunServers);
446 448
         initStropheUtil();
447 449
         initPing(this);
448 450
         initRayo();

+ 21
- 19
service/RTC/RTCEvents.js 查看文件

@@ -10,12 +10,6 @@ const RTCEvents = {
10 10
      */
11 11
     CREATE_OFFER_FAILED: 'rtc.create_offer_failed',
12 12
     RTC_READY: 'rtc.ready',
13
-
14
-    /**
15
-     * FIXME: rename to something closer to "local streams SDP changed"
16
-     * Indicates that the local sendrecv streams in local SDP are changed.
17
-     */
18
-    SENDRECV_STREAMS_CHANGED: 'rtc.sendrecv_streams_changed',
19 13
     DATA_CHANNEL_OPEN: 'rtc.data_channel_open',
20 14
     ENDPOINT_CONN_STATUS_CHANGED: 'rtc.endpoint_conn_status_changed',
21 15
     LASTN_CHANGED: 'rtc.lastn_changed',
@@ -26,14 +20,7 @@ const RTCEvents = {
26 20
 
27 21
     /**
28 22
      * Event fired when we remote track is added to the conference.
29
-     * The following structure is passed as an argument:
30
-     * {
31
-     *   stream: the WebRTC MediaStream instance
32
-     *   track: the WebRTC MediaStreamTrack
33
-     *   mediaType: the MediaType instance
34
-     *   owner: the MUC JID of the stream owner
35
-     *   muted: a boolean indicating initial 'muted' status of the track or
36
-      *         'null' if unknown
23
+     * 1st event argument is the added <tt>JitsiRemoteTrack</tt> instance.
37 24
      **/
38 25
     REMOTE_TRACK_ADDED: 'rtc.remote_track_added',
39 26
 
@@ -43,9 +30,7 @@ const RTCEvents = {
43 30
 
44 31
     /**
45 32
      * Indicates that the remote track has been removed from the conference.
46
-     * 1st event argument is the ID of the parent WebRTC stream to which
47
-     * the track being removed belongs to.
48
-     * 2nd event argument is the ID of the removed track.
33
+     * 1st event argument is the removed {@link JitsiRemoteTrack} instance.
49 34
      */
50 35
     REMOTE_TRACK_REMOVED: 'rtc.remote_track_removed',
51 36
 
@@ -70,8 +55,25 @@ const RTCEvents = {
70 55
      * Indicates that a message from another participant is received on
71 56
      * data channel.
72 57
      */
73
-    ENDPOINT_MESSAGE_RECEIVED:
74
-        'rtc.endpoint_message_received'
58
+    ENDPOINT_MESSAGE_RECEIVED: 'rtc.endpoint_message_received',
59
+
60
+    /**
61
+     * Designates an event indicating that the local ICE username fragment of
62
+     * the jingle session has changed.
63
+     * The first argument of the vent is <tt>TraceablePeerConnection</tt> which
64
+     * is the source of the event.
65
+     * The second argument is the actual "ufrag" string.
66
+     */
67
+    LOCAL_UFRAG_CHANGED: 'rtc.local_ufrag_changed',
68
+
69
+    /**
70
+     * Designates an event indicating that the local ICE username fragment of
71
+     * the jingle session has changed.
72
+     * The first argument of the vent is <tt>TraceablePeerConnection</tt> which
73
+     * is the source of the event.
74
+     * The second argument is the actual "ufrag" string.
75
+     */
76
+    REMOTE_UFRAG_CHANGED: 'rtc.remote_ufrag_changed'
75 77
 };
76 78
 
77 79
 module.exports = RTCEvents;

+ 14
- 0
service/RTC/SignalingEvents.js 查看文件

@@ -0,0 +1,14 @@
1
+/**
2
+ * Event triggered when participant's muted status changes.
3
+ * @param {string} endpointId the track owner's identifier (MUC nickname)
4
+ * @param {MediaType} mediaType "audio" or "video"
5
+ * @param {boolean} isMuted the new muted state
6
+ */
7
+export const PEER_MUTED_CHANGED = 'signaling.peerMuted';
8
+
9
+/**
10
+ * Event triggered when participant's video type changes.
11
+ * @param {string} endpointId the video owner's ID (MUC nickname)
12
+ * @param {VideoType} videoType the new value
13
+ */
14
+export const PEER_VIDEO_TYPE_CHANGED = 'signaling.peerVideoType';

+ 5
- 3
service/RTC/SignalingLayer.js 查看文件

@@ -1,19 +1,21 @@
1 1
 
2
+import Listenable from '../../modules/util/Listenable';
3
+
2 4
 /**
3 5
  * An object that carries the info about specific media type advertised by
4
- * participant in the signalling channel.
6
+ * participant in the signaling channel.
5 7
  * @typedef {Object} PeerMediaInfo
6 8
  * @property {boolean} muted indicates if the media is currently muted
7 9
  * @property {VideoType|undefined} videoType the type of the video if applicable
8 10
  */
9 11
 
10 12
 /**
11
- * Interface used to expose the information carried over the signalling channel
13
+ * Interface used to expose the information carried over the signaling channel
12 14
  * which is not available to the RTC module in the media SDP.
13 15
  *
14 16
  * @interface SignalingLayer
15 17
  */
16
-export default class SignalingLayer {
18
+export default class SignalingLayer extends Listenable {
17 19
 
18 20
     /**
19 21
      * Obtains the endpoint ID for given SSRC.

+ 15
- 7
service/xmpp/XMPPEvents.js 查看文件

@@ -10,6 +10,11 @@ const XMPPEvents = {
10 10
     AUTHENTICATION_REQUIRED: 'xmpp.authentication_required',
11 11
     BRIDGE_DOWN: 'xmpp.bridge_down',
12 12
 
13
+    /**
14
+     * Triggered when 'session-accept' is received from the responder.
15
+     */
16
+    CALL_ACCEPTED: 'xmpp.callaccepted.jingle',
17
+
13 18
     // Designates an event indicating that an offer (e.g. Jingle
14 19
     // session-initiate) was received.
15 20
     CALL_INCOMING: 'xmpp.callincoming.jingle',
@@ -22,6 +27,11 @@ const XMPPEvents = {
22 27
     CHAT_ERROR_RECEIVED: 'xmpp.chat_error_received',
23 28
     CONFERENCE_SETUP_FAILED: 'xmpp.conference_setup_failed',
24 29
 
30
+    /**
31
+     * This event is triggered when the ICE connects for the first time.
32
+     */
33
+    CONNECTION_ESTABLISHED: 'xmpp.connection.connected',
34
+
25 35
     // Designates an event indicating that the connection to the XMPP server
26 36
     // failed.
27 37
     CONNECTION_FAILED: 'xmpp.connection.failed',
@@ -181,16 +191,14 @@ const XMPPEvents = {
181 191
     // changed.
182 192
     SUBJECT_CHANGED: 'xmpp.subject_changed',
183 193
 
194
+    // FIXME: how does it belong to XMPP ? - it's detected by the PeerConnection
184 195
     // suspending detected
185 196
     SUSPEND_DETECTED: 'xmpp.suspend_detected',
186 197
 
187
-    // Designates an event indicating that the local ICE username fragment of
188
-    // the jingle session has changed.
189
-    LOCAL_UFRAG_CHANGED: 'xmpp.local_ufrag_changed',
190
-
191
-    // Designates an event indicating that the local ICE username fragment of
192
-    // the jingle session has changed.
193
-    REMOTE_UFRAG_CHANGED: 'xmpp.remote_ufrag_changed',
198
+    /**
199
+     * Event fired when 'transport-info' with new ICE candidates is received.
200
+     */
201
+    TRANSPORT_INFO: 'xmpp.transportinfo.jingle',
194 202
 
195 203
     // Designates an event indicating that the local ICE connection state has
196 204
     // changed.

正在加载...
取消
保存