Bläddra i källkod

Merge pull request #373 from jitsi/move_TPC

Move TraceablePeerConnection to RTC
dev1
bbaldino 9 år sedan
förälder
incheckning
12a91dcf17

+ 1
- 1
.jshintignore Visa fil

@@ -8,7 +8,6 @@ doc
8 8
 
9 9
 modules/xmpp/xmpp.js
10 10
 modules/xmpp/strophe.jingle.js
11
-modules/xmpp/TraceablePeerConnection.js
12 11
 modules/xmpp/SDPUtil.js
13 12
 modules/xmpp/SDP.js
14 13
 modules/xmpp/strophe.emuc.js
@@ -22,6 +21,7 @@ modules/RTC/adapter.screenshare.js
22 21
 modules/statistics/statistics.js
23 22
 modules/RTC/RTC.js
24 23
 modules/RTC/RTCUtils.js
24
+modules/RTC/TraceablePeerConnection.js
25 25
 modules/RTC/DataChannels.js
26 26
 modules/RTC/JitsiLocalTrack.js
27 27
 modules/RTC/JitsiRemoteTrack.js

+ 114
- 57
JitsiConference.js Visa fil

@@ -1,28 +1,30 @@
1
-/* global Strophe, Promise */
1
+/* global __filename, Strophe, Promise */
2 2
 
3
-var logger = require("jitsi-meet-logger").getLogger(__filename);
4
-import RTC from "./modules/RTC/RTC";
5
-import * as MediaType from "./service/RTC/MediaType";
6
-var XMPPEvents = require("./service/xmpp/XMPPEvents");
7
-var EventEmitter = require("events");
3
+import ComponentsVersions from "./modules/version/ComponentsVersions";
4
+import ConnectionQuality from "./modules/connectivity/ConnectionQuality";
5
+import { getLogger } from "jitsi-meet-logger";
6
+import GlobalOnErrorHandler from "./modules/util/GlobalOnErrorHandler";
7
+import EventEmitter from "events";
8 8
 import * as JitsiConferenceErrors from "./JitsiConferenceErrors";
9
+import JitsiConferenceEventManager from "./JitsiConferenceEventManager";
9 10
 import * as JitsiConferenceEvents from "./JitsiConferenceEvents";
11
+import JitsiDTMFManager from './modules/DTMF/JitsiDTMFManager';
10 12
 import JitsiParticipant from "./JitsiParticipant";
11
-var Statistics = require("./modules/statistics/statistics");
12
-var JitsiDTMFManager = require('./modules/DTMF/JitsiDTMFManager');
13 13
 import JitsiTrackError from "./JitsiTrackError";
14 14
 import * as JitsiTrackErrors from "./JitsiTrackErrors";
15 15
 import * as JitsiTrackEvents from "./JitsiTrackEvents";
16
-var ComponentsVersions = require("./modules/version/ComponentsVersions");
17
-var GlobalOnErrorHandler = require("./modules/util/GlobalOnErrorHandler");
18
-var JitsiConferenceEventManager = require("./JitsiConferenceEventManager");
19
-var VideoType = require('./service/RTC/VideoType');
20
-var RTCBrowserType = require("./modules/RTC/RTCBrowserType.js");
21
-var Transcriber = require("./modules/transcription/transcriber");
16
+import * as MediaType from "./service/RTC/MediaType";
22 17
 import ParticipantConnectionStatus
23 18
     from "./modules/connectivity/ParticipantConnectionStatus";
19
+import RTC from "./modules/RTC/RTC";
20
+import RTCBrowserType from "./modules/RTC/RTCBrowserType.js";
21
+import * as RTCEvents from "./service/RTC/RTCEvents";
22
+import Statistics from "./modules/statistics/statistics";
24 23
 import TalkMutedDetection from "./modules/TalkMutedDetection";
25
-import ConnectionQuality from "./modules/connectivity/ConnectionQuality";
24
+import Transcriber from "./modules/transcription/transcriber";
25
+import VideoType from './service/RTC/VideoType';
26
+
27
+const logger = getLogger(__filename);
26 28
 
27 29
 /**
28 30
  * Creates a JitsiConference object with the given name and properties.
@@ -170,7 +172,7 @@ JitsiConference.prototype.leave = function () {
170 172
         this.participantConnectionStatus = null;
171 173
     }
172 174
 
173
-    this.getLocalTracks().forEach(track => this.onTrackRemoved(track));
175
+    this.getLocalTracks().forEach(track => this.onLocalTrackRemoved(track));
174 176
 
175 177
     this.rtc.closeAllDataChannels();
176 178
     if (this.statistics)
@@ -262,17 +264,27 @@ JitsiConference.prototype.getExternalAuthUrl = function (urlForPopup) {
262 264
 JitsiConference.prototype.getLocalTracks = function (mediaType) {
263 265
     let tracks = [];
264 266
     if (this.rtc) {
265
-        tracks = this.rtc.localTracks.slice();
266
-    }
267
-    if (mediaType !== undefined) {
268
-        tracks = tracks.filter(
269
-            (track) => {
270
-                return track && track.getType && track.getType() === mediaType;
271
-            });
267
+        tracks = this.rtc.getLocalTracks(mediaType);
272 268
     }
273 269
     return tracks;
274 270
 };
275 271
 
272
+/**
273
+ * Obtains local audio track.
274
+ * @return {JitsiLocalTrack|null}
275
+ */
276
+JitsiConference.prototype.getLocalAudioTrack = function () {
277
+    return this.rtc ? this.rtc.getLocalAudioTrack() : null;
278
+};
279
+
280
+/**
281
+ * Obtains local video track.
282
+ * @return {JitsiLocalTrack|null}
283
+ */
284
+JitsiConference.prototype.getLocalVideoTrack = function () {
285
+    return this.rtc ? this.rtc.getLocalVideoTrack() : null;
286
+};
287
+
276 288
 /**
277 289
  * Attaches a handler for events(For example - "participant joined".) in the conference. All possible event are defined
278 290
  * in JitsiConferenceEvents.
@@ -394,17 +406,15 @@ JitsiConference.prototype.getTranscriber = function(){
394 406
     if (this.transcriber === undefined){
395 407
         this.transcriber = new Transcriber();
396 408
         //add all existing local audio tracks to the transcriber
397
-        this.rtc.localTracks.forEach(function (localTrack) {
398
-            if (localTrack.isAudioTrack()){
399
-                this.transcriber.addTrack(localTrack);
400
-            }
401
-        }.bind(this));
409
+        const localAudioTracks = this.getLocalTracks(MediaType.AUDIO);
410
+        for (const localAudio of localAudioTracks) {
411
+            this.transcriber.addTrack(localAudio);
412
+        }
402 413
         //and all remote audio tracks
403
-        this.rtc.remoteTracks.forEach(function (remoteTrack){
404
-            if (remoteTrack.isAudioTrack()){
405
-                this.transcriber.addTrack(remoteTrack);
406
-            }
407
-        }.bind(this));
414
+        const remoteAudioTracks = this.rtc.getRemoteTracks(MediaType.AUDIO);
415
+        for (const remoteTrack of remoteAudioTracks){
416
+            this.transcriber.addTrack(remoteTrack);
417
+        }
408 418
     }
409 419
     return this.transcriber;
410 420
 };
@@ -463,7 +473,7 @@ JitsiConference.prototype._fireMuteChangeEvent = function (track) {
463 473
  * Clear JitsiLocalTrack properties and listeners.
464 474
  * @param track the JitsiLocalTrack object.
465 475
  */
466
-JitsiConference.prototype.onTrackRemoved = function (track) {
476
+JitsiConference.prototype.onLocalTrackRemoved = function (track) {
467 477
     track._setSSRC(null);
468 478
     track._setConference(null);
469 479
     this.rtc.removeLocalTrack(track);
@@ -471,7 +481,7 @@ JitsiConference.prototype.onTrackRemoved = function (track) {
471 481
         track.muteHandler);
472 482
     track.removeEventListener(JitsiTrackEvents.TRACK_AUDIO_LEVEL_CHANGED,
473 483
         track.audioLevelHandler);
474
-    this.room.removeListener(XMPPEvents.SENDRECV_STREAMS_CHANGED,
484
+    this.rtc.removeListener(RTCEvents.SENDRECV_STREAMS_CHANGED,
475 485
         track.ssrcHandler);
476 486
 
477 487
     // send event for stopping screen sharing
@@ -519,18 +529,19 @@ JitsiConference.prototype.replaceTrack = function (oldTrack, newTrack) {
519 529
         newTrack.ssrcHandler = function (conference, ssrcMap) {
520 530
             if (ssrcMap[this.getMSID()]) {
521 531
                 this._setSSRC(ssrcMap[this.getMSID()]);
522
-                conference.room.removeListener(XMPPEvents.SENDRECV_STREAMS_CHANGED,
532
+                conference.rtc.removeListener(
533
+                    RTCEvents.SENDRECV_STREAMS_CHANGED,
523 534
                     this.ssrcHandler);
524 535
             }
525 536
         }.bind(newTrack, this);
526
-        this.room.addListener(XMPPEvents.SENDRECV_STREAMS_CHANGED,
537
+        this.rtc.addListener(RTCEvents.SENDRECV_STREAMS_CHANGED,
527 538
             newTrack.ssrcHandler);
528 539
     }
529 540
     // Now replace the stream at the lower levels
530 541
     return this._doReplaceTrack(oldTrack, newTrack)
531 542
         .then(() => {
532 543
             if (oldTrack) {
533
-                this.onTrackRemoved(oldTrack);
544
+                this.onLocalTrackRemoved(oldTrack);
534 545
             }
535 546
             if (newTrack) {
536 547
                 // Now handle the addition of the newTrack at the JitsiConference level
@@ -895,16 +906,17 @@ JitsiConference.prototype.onDisplayNameChanged = function (jid, displayName) {
895 906
 };
896 907
 
897 908
 /**
898
- * Notifies this JitsiConference that a JitsiRemoteTrack was added (into the
899
- * ChatRoom of this JitsiConference).
909
+ * Notifies this JitsiConference that a JitsiRemoteTrack was added into
910
+ * the conference.
900 911
  *
901 912
  * @param {JitsiRemoteTrack} track the JitsiRemoteTrack which was added to this
902 913
  * JitsiConference
903 914
  */
904
-JitsiConference.prototype.onTrackAdded = function (track) {
905
-    var id = track.getParticipantId();
906
-    var participant = this.getParticipantById(id);
915
+JitsiConference.prototype.onRemoteTrackAdded = function (track) {
916
+    const id = track.getParticipantId();
917
+    const participant = this.getParticipantById(id);
907 918
     if (!participant) {
919
+        logger.error(`No participant found for id: ${id}`);
908 920
         return;
909 921
     }
910 922
 
@@ -915,7 +927,7 @@ JitsiConference.prototype.onTrackAdded = function (track) {
915 927
         this.transcriber.addTrack(track);
916 928
     }
917 929
 
918
-    var emitter = this.eventEmitter;
930
+    const emitter = this.eventEmitter;
919 931
     track.addEventListener(
920 932
         JitsiTrackEvents.TRACK_MUTE_CHANGED,
921 933
         function () {
@@ -935,6 +947,47 @@ JitsiConference.prototype.onTrackAdded = function (track) {
935 947
     emitter.emit(JitsiConferenceEvents.TRACK_ADDED, track);
936 948
 };
937 949
 
950
+/**
951
+ * Notifies this JitsiConference that a JitsiRemoteTrack was removed from
952
+ * the conference.
953
+ *
954
+ * @param {JitsiRemoteTrack} removedTrack
955
+ */
956
+JitsiConference.prototype.onRemoteTrackRemoved = function (removedTrack) {
957
+    let consumed = false;
958
+
959
+    this.getParticipants().forEach(function(participant) {
960
+        const tracks = participant.getTracks();
961
+
962
+        for(let i = 0; i < tracks.length; i++) {
963
+            if (tracks[i] === removedTrack) {
964
+                // Since the tracks have been compared and are
965
+                // considered equal the result of splice can be ignored.
966
+                participant._tracks.splice(i, 1);
967
+
968
+                this.eventEmitter.emit(
969
+                    JitsiConferenceEvents.TRACK_REMOVED, removedTrack);
970
+
971
+                if (this.transcriber){
972
+                    this.transcriber.removeTrack(removedTrack);
973
+                }
974
+
975
+                consumed = true;
976
+
977
+                break;
978
+            }
979
+        }
980
+    }, this);
981
+
982
+    if (!consumed) {
983
+        logger.error(
984
+            "Failed to match remote track on remove"
985
+                + " with any of the participants",
986
+            removedTrack.getStreamId(),
987
+            removedTrack.getParticipantId());
988
+    }
989
+};
990
+
938 991
 /**
939 992
  * Handles incoming call event.
940 993
  */
@@ -978,14 +1031,14 @@ function (jingleSession, jingleOffer, now) {
978 1031
             label: crossRegion
979 1032
         });
980 1033
     try {
981
-        jingleSession.initialize(false /* initiator */,this.room);
1034
+        jingleSession.initialize(false /* initiator */, this.room, this.rtc);
982 1035
     } catch (error) {
983 1036
         GlobalOnErrorHandler.callErrorHandler(error);
984 1037
     }
985 1038
 
986 1039
     this.rtc.initializeDataChannels(jingleSession.peerconnection);
987 1040
     // Add local Tracks to the ChatRoom
988
-    this.rtc.localTracks.forEach(function(localTrack) {
1041
+    this.getLocalTracks().forEach(function(localTrack) {
989 1042
         var ssrcInfo = null;
990 1043
         /**
991 1044
          * We don't do this for Firefox because, on Firefox, we keep the
@@ -1081,13 +1134,13 @@ JitsiConference.prototype.onCallEnded
1081 1134
     // will learn what their SSRC from the new PeerConnection which will be
1082 1135
     // created on incoming call event.
1083 1136
     var self = this;
1084
-    this.rtc.localTracks.forEach(function(localTrack) {
1137
+    this.getLocalTracks().forEach(function(localTrack) {
1085 1138
         // Reset SSRC as it will no longer be valid
1086 1139
         localTrack._setSSRC(null);
1087 1140
         // Bind the handler to fetch new SSRC, it will un register itself once
1088 1141
         // it reads the values
1089
-        self.room.addListener(
1090
-            XMPPEvents.SENDRECV_STREAMS_CHANGED, localTrack.ssrcHandler);
1142
+        self.rtc.addListener(
1143
+            RTCEvents.SENDRECV_STREAMS_CHANGED, localTrack.ssrcHandler);
1091 1144
     });
1092 1145
 };
1093 1146
 
@@ -1134,21 +1187,25 @@ JitsiConference.prototype.myUserId = function () {
1134 1187
 };
1135 1188
 
1136 1189
 JitsiConference.prototype.sendTones = function (tones, duration, pause) {
1190
+    // FIXME P2P 'dtmfManager' must be cleared, after switching jingleSessions
1137 1191
     if (!this.dtmfManager) {
1138
-        var connection = this.xmpp.connection.jingle.activecall.peerconnection;
1139
-        if (!connection) {
1140
-            logger.warn("cannot sendTones: no conneciton");
1192
+        if (!this.jingleSession) {
1193
+            logger.warn("cannot sendTones: no jingle session");
1141 1194
             return;
1142 1195
         }
1143 1196
 
1144
-        var tracks = this.getLocalTracks().filter(function (track) {
1145
-            return track.isAudioTrack();
1146
-        });
1147
-        if (!tracks.length) {
1197
+        const peerConnection = this.jingleSession.peerconnection;
1198
+        if (!peerConnection) {
1199
+            logger.warn("cannot sendTones: no peer connection");
1200
+            return;
1201
+        }
1202
+
1203
+        const localAudio = this.getLocalAudioTrack();
1204
+        if (!localAudio) {
1148 1205
             logger.warn("cannot sendTones: no local audio stream");
1149 1206
             return;
1150 1207
         }
1151
-        this.dtmfManager = new JitsiDTMFManager(tracks[0], connection);
1208
+        this.dtmfManager = new JitsiDTMFManager(localAudio, peerConnection);
1152 1209
     }
1153 1210
 
1154 1211
     this.dtmfManager.sendTones(tones, duration, pause);

+ 42
- 66
JitsiConferenceEventManager.js Visa fil

@@ -43,41 +43,6 @@ JitsiConferenceEventManager.prototype.setupChatRoomListeners = function () {
43 43
         conference.rtc.closeAllDataChannels();
44 44
     });
45 45
 
46
-    chatRoom.addListener(XMPPEvents.REMOTE_TRACK_ADDED,
47
-        function (data) {
48
-            var track = conference.rtc.createRemoteTrack(data);
49
-            if (track) {
50
-                conference.onTrackAdded(track);
51
-            }
52
-        }
53
-    );
54
-    chatRoom.addListener(XMPPEvents.REMOTE_TRACK_REMOVED,
55
-        function (streamId, trackId) {
56
-            conference.getParticipants().forEach(function(participant) {
57
-                var tracks = participant.getTracks();
58
-                for(var i = 0; i < tracks.length; i++) {
59
-                    if(tracks[i]
60
-                        && tracks[i].getStreamId() == streamId
61
-                        && tracks[i].getTrackId() == trackId) {
62
-                        var track = participant._tracks.splice(i, 1)[0];
63
-
64
-                        conference.rtc.removeRemoteTrack(
65
-                            participant.getId(), track.getType());
66
-
67
-                        conference.eventEmitter.emit(
68
-                            JitsiConferenceEvents.TRACK_REMOVED, track);
69
-
70
-                        if(conference.transcriber){
71
-                            conference.transcriber.removeTrack(track);
72
-                        }
73
-
74
-                        return;
75
-                    }
76
-                }
77
-            });
78
-        }
79
-    );
80
-
81 46
     chatRoom.addListener(XMPPEvents.AUDIO_MUTED_BY_FOCUS,
82 47
         function (value) {
83 48
             // set isMutedByFocus when setAudioMute Promise ends
@@ -413,31 +378,11 @@ JitsiConferenceEventManager.prototype.setupChatRoomListeners = function () {
413 378
     });
414 379
 
415 380
     if(conference.statistics) {
381
+        // FIXME ICE related events should end up in RTCEvents eventually
416 382
         chatRoom.addListener(XMPPEvents.CONNECTION_ICE_FAILED,
417 383
             function (pc) {
418 384
                 conference.statistics.sendIceConnectionFailedEvent(pc);
419 385
             });
420
-
421
-        chatRoom.addListener(XMPPEvents.CREATE_OFFER_FAILED,
422
-            function (e, pc) {
423
-                conference.statistics.sendCreateOfferFailed(e, pc);
424
-            });
425
-
426
-        chatRoom.addListener(XMPPEvents.CREATE_ANSWER_FAILED,
427
-            function (e, pc) {
428
-                conference.statistics.sendCreateAnswerFailed(e, pc);
429
-            });
430
-
431
-        chatRoom.addListener(XMPPEvents.SET_LOCAL_DESCRIPTION_FAILED,
432
-            function (e, pc) {
433
-                conference.statistics.sendSetLocalDescFailed(e, pc);
434
-            });
435
-
436
-        chatRoom.addListener(XMPPEvents.SET_REMOTE_DESCRIPTION_FAILED,
437
-            function (e, pc) {
438
-                conference.statistics.sendSetRemoteDescFailed(e, pc);
439
-            });
440
-
441 386
         chatRoom.addListener(XMPPEvents.ADD_ICE_CANDIDATE_FAILED,
442 387
             function (e, pc) {
443 388
                 conference.statistics.sendAddIceCandidateFailed(e, pc);
@@ -449,12 +394,21 @@ JitsiConferenceEventManager.prototype.setupChatRoomListeners = function () {
449 394
  * Setups event listeners related to conference.rtc
450 395
  */
451 396
 JitsiConferenceEventManager.prototype.setupRTCListeners = function () {
452
-    var conference = this.conference;
397
+    const conference = this.conference;
398
+    const rtc = conference.rtc;
453 399
 
454
-    this.rtcForwarder = new EventEmitterForwarder(conference.rtc,
455
-        this.conference.eventEmitter);
400
+    this.rtcForwarder
401
+        = new EventEmitterForwarder(rtc, this.conference.eventEmitter);
456 402
 
457
-    conference.rtc.addListener(RTCEvents.DOMINANT_SPEAKER_CHANGED,
403
+    rtc.addListener(
404
+        RTCEvents.REMOTE_TRACK_ADDED,
405
+        conference.onRemoteTrackAdded.bind(conference));
406
+
407
+    rtc.addListener(
408
+        RTCEvents.REMOTE_TRACK_REMOVED,
409
+        conference.onRemoteTrackRemoved.bind(conference));
410
+
411
+    rtc.addListener(RTCEvents.DOMINANT_SPEAKER_CHANGED,
458 412
         function (id) {
459 413
             if(conference.lastDominantSpeaker !== id && conference.room) {
460 414
                 conference.lastDominantSpeaker = id;
@@ -467,7 +421,7 @@ JitsiConferenceEventManager.prototype.setupRTCListeners = function () {
467 421
             }
468 422
         });
469 423
 
470
-    conference.rtc.addListener(RTCEvents.DATA_CHANNEL_OPEN, function () {
424
+    rtc.addListener(RTCEvents.DATA_CHANNEL_OPEN, function () {
471 425
         var now = window.performance.now();
472 426
         logger.log("(TIME) data channel opened ", now);
473 427
         conference.room.connectionTimes["data.channel.opened"] = now;
@@ -481,12 +435,12 @@ JitsiConferenceEventManager.prototype.setupRTCListeners = function () {
481 435
     this.rtcForwarder.forward(RTCEvents.LASTN_ENDPOINT_CHANGED,
482 436
         JitsiConferenceEvents.LAST_N_ENDPOINTS_CHANGED);
483 437
 
484
-    conference.rtc.addListener(RTCEvents.AVAILABLE_DEVICES_CHANGED,
438
+    rtc.addListener(RTCEvents.AVAILABLE_DEVICES_CHANGED,
485 439
         function (devices) {
486 440
             conference.room.updateDeviceAvailability(devices);
487 441
         });
488 442
 
489
-    conference.rtc.addListener(RTCEvents.ENDPOINT_MESSAGE_RECEIVED,
443
+    rtc.addListener(RTCEvents.ENDPOINT_MESSAGE_RECEIVED,
490 444
         function (from, payload) {
491 445
             const participant = conference.getParticipantById(from);
492 446
             if (participant) {
@@ -499,6 +453,28 @@ JitsiConferenceEventManager.prototype.setupRTCListeners = function () {
499 453
                     "for not existing participant: " + from, payload);
500 454
             }
501 455
         });
456
+
457
+    if (conference.statistics) {
458
+        rtc.addListener(RTCEvents.CREATE_ANSWER_FAILED,
459
+            (e, pc) => {
460
+                conference.statistics.sendCreateAnswerFailed(e, pc);
461
+            });
462
+
463
+        rtc.addListener(RTCEvents.CREATE_OFFER_FAILED,
464
+            (e, pc) => {
465
+                conference.statistics.sendCreateOfferFailed(e, pc);
466
+            });
467
+
468
+        rtc.addListener(RTCEvents.SET_LOCAL_DESCRIPTION_FAILED,
469
+            (e, pc) => {
470
+                conference.statistics.sendSetLocalDescFailed(e, pc);
471
+            });
472
+
473
+        rtc.addListener(RTCEvents.SET_REMOTE_DESCRIPTION_FAILED,
474
+            (e, pc) => {
475
+                conference.statistics.sendSetRemoteDescFailed(e, pc);
476
+            });
477
+    }
502 478
 };
503 479
 
504 480
 /**
@@ -597,9 +573,9 @@ JitsiConferenceEventManager.prototype.setupStatisticsListeners = function () {
597 573
     });
598 574
 
599 575
     conference.statistics.addByteSentStatsListener(function (stats) {
600
-        conference.getLocalTracks().forEach(function (track) {
601
-            var ssrc = track.getSSRC();
602
-            if(!track.isAudioTrack() || !ssrc || !stats.hasOwnProperty(ssrc))
576
+        conference.getLocalTracks(MediaType.AUDIO).forEach(function (track) {
577
+            const ssrc = track.getSSRC();
578
+            if (!ssrc || !stats.hasOwnProperty(ssrc))
603 579
                 return;
604 580
 
605 581
             track._setByteSent(stats[ssrc]);

+ 18
- 14
modules/RTC/JitsiRemoteTrack.js Visa fil

@@ -1,4 +1,4 @@
1
-/* global Strophe */
1
+/* global */
2 2
 
3 3
 var JitsiTrack = require("./JitsiTrack");
4 4
 import * as JitsiTrackEvents from "../../JitsiTrackEvents";
@@ -12,22 +12,25 @@ var ttfmTrackerVideoAttached = false;
12 12
 
13 13
 /**
14 14
  * Represents a single media track (either audio or video).
15
- * @param rtc {RTC} the RTC service instance.
16
- * @param ownerJid the MUC JID of the track owner
17
- * @param stream WebRTC MediaStream, parent of the track
18
- * @param track underlying WebRTC MediaStreamTrack for new JitsiRemoteTrack
19
- * @param mediaType the MediaType of the JitsiRemoteTrack
20
- * @param videoType the VideoType of the JitsiRemoteTrack
21
- * @param ssrc the SSRC number of the Media Stream
22
- * @param muted initial muted state of the JitsiRemoteTrack
15
+ * @param {RTC} rtc the RTC service instance.
16
+ * @param {JitsiConference} conference the conference to which this track
17
+ *        belongs to
18
+ * @param {string} ownerEndpointId the endpoint ID of the track owner
19
+ * @param {MediaStream} stream WebRTC MediaStream, parent of the track
20
+ * @param {MediaStreamTrack} track underlying WebRTC MediaStreamTrack for
21
+ *        the new JitsiRemoteTrack
22
+ * @param {MediaType} mediaType the type of the media
23
+ * @param {VideoType} videoType the type of the video if applicable
24
+ * @param {string} ssrc the SSRC number of the Media Stream
25
+ * @param {boolean} muted the initial muted state
23 26
  * @constructor
24 27
  */
25
-function JitsiRemoteTrack(rtc, conference, ownerJid, stream, track, mediaType, videoType,
26
-                          ssrc, muted) {
28
+function JitsiRemoteTrack(rtc, conference, ownerEndpointId, stream, track,
29
+                          mediaType, videoType, ssrc, muted) {
27 30
     JitsiTrack.call(
28 31
         this, conference, stream, track, function () {}, mediaType, videoType, ssrc);
29 32
     this.rtc = rtc;
30
-    this.peerjid = ownerJid;
33
+    this.ownerEndpointId = ownerEndpointId;
31 34
     this.muted = muted;
32 35
     // we want to mark whether the track has been ever muted
33 36
     // to detect ttfm events for startmuted conferences, as it can significantly
@@ -98,10 +101,11 @@ JitsiRemoteTrack.prototype.isMuted = function () {
98 101
 
99 102
 /**
100 103
  * Returns the participant id which owns the track.
101
- * @returns {string} the id of the participants.
104
+ * @returns {string} the id of the participants. It corresponds to the Colibri
105
+ * endpoint id/MUC nickname in case of Jitsi-meet.
102 106
  */
103 107
 JitsiRemoteTrack.prototype.getParticipantId = function() {
104
-    return Strophe.getResourceFromJid(this.peerjid);
108
+    return this.ownerEndpointId;
105 109
 };
106 110
 
107 111
 /**

+ 0
- 9
modules/RTC/JitsiTrack.js Visa fil

@@ -308,15 +308,6 @@ JitsiTrack.prototype.dispose = function () {
308 308
 JitsiTrack.prototype.isScreenSharing = function() {
309 309
 };
310 310
 
311
-/**
312
- * FIXME remove hack in SDP.js and this method
313
- * Returns id of the track.
314
- * @returns {string|null} id of the track or null if this is fake track.
315
- */
316
-JitsiTrack.prototype._getId = function () {
317
-    return this.getTrackId();
318
-};
319
-
320 311
 /**
321 312
  * Returns id of the track.
322 313
  * @returns {string|null} id of the track or null if this is fake track.

+ 240
- 89
modules/RTC/RTC.js Visa fil

@@ -1,4 +1,4 @@
1
-/* global Strophe */
1
+/* global */
2 2
 
3 3
 var logger = require("jitsi-meet-logger").getLogger(__filename);
4 4
 var RTCEvents = require("../../service/RTC/RTCEvents.js");
@@ -9,6 +9,7 @@ import * as JitsiTrackErrors from "../../JitsiTrackErrors";
9 9
 var DataChannels = require("./DataChannels");
10 10
 var JitsiRemoteTrack = require("./JitsiRemoteTrack.js");
11 11
 var MediaType = require("../../service/RTC/MediaType");
12
+var TraceablePeerConnection = require("./TraceablePeerConnection");
12 13
 var VideoType = require("../../service/RTC/VideoType");
13 14
 var GlobalOnErrorHandler = require("../util/GlobalOnErrorHandler");
14 15
 import Listenable from "../util/Listenable";
@@ -40,11 +41,20 @@ export default class RTC extends Listenable {
40 41
     constructor(conference, options = {}) {
41 42
         super();
42 43
         this.conference = conference;
44
+        /**
45
+         * A map of active <tt>TraceablePeerConnection</tt>.
46
+         * @type {Map.<number, TraceablePeerConnection>}
47
+         */
48
+        this.peerConnections = new Map();
49
+        /**
50
+         * The counter used to generated id numbers assigned to peer connections
51
+         * @type {number}
52
+         */
53
+        this.peerConnectionIdCounter = 1;
54
+
43 55
         this.localTracks = [];
44 56
         //FIXME: We should support multiple streams per jid.
45 57
         this.remoteTracks = {};
46
-        this.localAudio = null;
47
-        this.localVideo = null;
48 58
         this.options = options;
49 59
         // A flag whether we had received that the data channel had opened
50 60
         // we can get this flag out of sync if for some reason data channel got
@@ -57,12 +67,10 @@ export default class RTC extends Listenable {
57 67
         if (RTCUtils.isDeviceChangeAvailable('output')) {
58 68
             RTCUtils.addListener(RTCEvents.AUDIO_OUTPUT_DEVICE_CHANGED,
59 69
                 (deviceId) => {
60
-                    for (var key in this.remoteTracks) {
61
-                        if (this.remoteTracks.hasOwnProperty(key)
62
-                            && this.remoteTracks[key].audio) {
63
-                            this.remoteTracks[key].audio
64
-                                .setAudioOutput(deviceId);
65
-                        }
70
+                    const remoteAudioTracks
71
+                        = this.getRemoteTracks(MediaType.AUDIO);
72
+                    for (const track of remoteAudioTracks) {
73
+                        track.setAudioOutput(deviceId);
66 74
                     }
67 75
                 });
68 76
         }
@@ -197,6 +205,51 @@ export default class RTC extends Listenable {
197 205
         return RTCUtils.getDeviceAvailability();
198 206
     }
199 207
 
208
+    /**
209
+     * Creates new <tt>TraceablePeerConnection</tt>
210
+     * @param {SignalingLayer} signaling the signaling layer that will
211
+     * provide information about the media or participants which is not carried
212
+     * over SDP.
213
+     * @param {Object} iceConfig an object describing the ICE config like
214
+     * defined in the WebRTC specification.
215
+     * @param {Object} options the config options
216
+     * @param {boolean} options.disableSimulcast if set to 'true' will disable
217
+     * the simulcast
218
+     * @param {boolean} options.disableRtx if set to 'true' will disable the RTX
219
+     * @param {boolean} options.preferH264 if set to 'true' H264 will be
220
+     * preferred over other video codecs.
221
+     * @return {TraceablePeerConnection}
222
+     */
223
+    createPeerConnection (signaling, iceConfig, options) {
224
+        const newConnection
225
+            = new TraceablePeerConnection(
226
+                this,
227
+                this.peerConnectionIdCounter,
228
+                signaling, iceConfig, RTC.getPCConstraints(), options);
229
+
230
+        this.peerConnections.set(newConnection.id, newConnection);
231
+        this.peerConnectionIdCounter += 1;
232
+        return newConnection;
233
+    }
234
+
235
+    /**
236
+     * Removed given peer connection from this RTC module instance.
237
+     * @param {TraceablePeerConnection} traceablePeerConnection
238
+     * @return {boolean} <tt>true</tt> if the given peer connection was removed
239
+     * successfully or <tt>false</tt> if there was no peer connection mapped in
240
+     * this RTC instance.
241
+     */
242
+    _removePeerConnection (traceablePeerConnection) {
243
+        const id = traceablePeerConnection.id;
244
+        if (this.peerConnections.has(id)) {
245
+            // NOTE Remote tracks are not removed here.
246
+            this.peerConnections.delete(id);
247
+            return true;
248
+        } else {
249
+            return false;
250
+        }
251
+    }
252
+
200 253
     addLocalTrack (track) {
201 254
         if (!track)
202 255
             throw new Error('track must not be null nor undefined');
@@ -204,20 +257,68 @@ export default class RTC extends Listenable {
204 257
         this.localTracks.push(track);
205 258
 
206 259
         track.conference = this.conference;
207
-
208
-        if (track.isAudioTrack()) {
209
-            this.localAudio = track;
210
-        } else {
211
-            this.localVideo = track;
212
-        }
213 260
     }
214 261
 
215 262
     /**
216 263
      * Get local video track.
217
-     * @returns {JitsiLocalTrack}
264
+     * @returns {JitsiLocalTrack|undefined}
218 265
      */
219 266
     getLocalVideoTrack () {
220
-        return this.localVideo;
267
+        const localVideo = this.getLocalTracks(MediaType.VIDEO);
268
+        return localVideo.length ? localVideo[0] : undefined;
269
+    }
270
+
271
+    /**
272
+     * Get local audio track.
273
+     * @returns {JitsiLocalTrack|undefined}
274
+     */
275
+    getLocalAudioTrack () {
276
+        const localAudio = this.getLocalTracks(MediaType.AUDIO);
277
+        return localAudio.length ? localAudio[0] : undefined;
278
+    }
279
+
280
+    /**
281
+     * Returns the local tracks of the given media type, or all local tracks if
282
+     * no specific type is given.
283
+     * @param {MediaType} [mediaType] optional media type filter
284
+     * (audio or video).
285
+     */
286
+    getLocalTracks (mediaType) {
287
+        let tracks = this.localTracks.slice();
288
+        if (mediaType !== undefined) {
289
+            tracks = tracks.filter(
290
+                (track) => { return track.getType() === mediaType; });
291
+        }
292
+        return tracks;
293
+    }
294
+
295
+    /**
296
+     * Obtains all remote tracks currently known to this RTC module instance.
297
+     * @param {MediaType} [mediaType] the remote tracks will be filtered
298
+     * by their media type if this argument is specified.
299
+     * @return {Array<JitsiRemoteTrack>}
300
+     */
301
+    getRemoteTracks (mediaType) {
302
+        const remoteTracks = [];
303
+        const remoteEndpoints = Object.keys(this.remoteTracks);
304
+
305
+        for (const endpoint of remoteEndpoints) {
306
+            const endpointMediaTypes = Object.keys(this.remoteTracks[endpoint]);
307
+
308
+            for (const trackMediaType of endpointMediaTypes) {
309
+                // per media type filtering
310
+                if (mediaType && mediaType !== trackMediaType) {
311
+                    continue;
312
+                }
313
+
314
+                const mediaTrack = this.remoteTracks[endpoint][trackMediaType];
315
+
316
+                if (mediaTrack) {
317
+                    remoteTracks.push(mediaTrack);
318
+                }
319
+            }
320
+        }
321
+        return remoteTracks;
221 322
     }
222 323
 
223 324
     /**
@@ -260,94 +361,135 @@ export default class RTC extends Listenable {
260 361
      * @returns {Promise}
261 362
      */
262 363
     setAudioMute (value) {
263
-        var mutePromises = [];
264
-        for(var i = 0; i < this.localTracks.length; i++) {
265
-            var track = this.localTracks[i];
266
-            if(track.getType() !== MediaType.AUDIO) {
267
-                continue;
268
-            }
364
+        const mutePromises = [];
365
+        this.getLocalTracks(MediaType.AUDIO).forEach(function(audioTrack){
269 366
             // this is a Promise
270
-            mutePromises.push(value ? track.mute() : track.unmute());
271
-        }
367
+            mutePromises.push(value ? audioTrack.mute() : audioTrack.unmute());
368
+        });
272 369
         // we return a Promise from all Promises so we can wait for their execution
273 370
         return Promise.all(mutePromises);
274 371
     }
275 372
 
276 373
     removeLocalTrack (track) {
277
-        var pos = this.localTracks.indexOf(track);
374
+        const pos = this.localTracks.indexOf(track);
278 375
         if (pos === -1) {
279 376
             return;
280 377
         }
281 378
 
282 379
         this.localTracks.splice(pos, 1);
283
-
284
-        if (track.isAudioTrack()) {
285
-            this.localAudio = null;
286
-        } else {
287
-            this.localVideo = null;
288
-        }
289 380
     }
290 381
 
291 382
     /**
292
-     * Initializes a new JitsiRemoteTrack instance with the data provided by (a)
293
-     * ChatRoom to XMPPEvents.REMOTE_TRACK_ADDED.
383
+     * Initializes a new JitsiRemoteTrack instance with the data provided by
384
+     * the signaling layer and SDP.
294 385
      *
295
-     * @param {Object} event the data provided by (a) ChatRoom to
296
-     * XMPPEvents.REMOTE_TRACK_ADDED to (a)
297
-     */
298
-    createRemoteTrack (event) {
299
-        var ownerJid = event.owner;
300
-        var remoteTrack = new JitsiRemoteTrack(
301
-            this, this.conference, ownerJid, event.stream, event.track,
302
-            event.mediaType, event.videoType, event.ssrc, event.muted);
303
-        var resource = Strophe.getResourceFromJid(ownerJid);
304
-        var remoteTracks
305
-            = this.remoteTracks[resource] || (this.remoteTracks[resource] = {});
306
-        var mediaType = remoteTrack.getType();
386
+     * @param {string} ownerEndpointId
387
+     * @param {MediaStream} stream
388
+     * @param {MediaStreamTrack} track
389
+     * @param {MediaType} mediaType
390
+     * @param {VideoType|undefined} videoType
391
+     * @param {string} ssrc
392
+     * @param {boolean} muted
393
+     */
394
+    _createRemoteTrack (ownerEndpointId,
395
+                        stream, track, mediaType, videoType, ssrc, muted) {
396
+        const remoteTrack
397
+            = new JitsiRemoteTrack(
398
+                this, this.conference, ownerEndpointId, stream, track,
399
+                mediaType, videoType, ssrc, muted);
400
+        const remoteTracks
401
+            = this.remoteTracks[ownerEndpointId]
402
+                || (this.remoteTracks[ownerEndpointId] = {});
403
+
307 404
         if (remoteTracks[mediaType]) {
308
-            logger.warn("Overwriting remote track!", resource, mediaType);
405
+            logger.error(
406
+                "Overwriting remote track!", ownerEndpointId, mediaType);
309 407
         }
310 408
         remoteTracks[mediaType] = remoteTrack;
311
-        return remoteTrack;
409
+
410
+        this.eventEmitter.emit(RTCEvents.REMOTE_TRACK_ADDED, remoteTrack);
312 411
     }
313 412
 
314 413
     /**
315
-     * Removes all JitsiRemoteTracks associated with given MUC nickname (resource
316
-     * part of the JID). Returns array of removed tracks.
414
+     * Removes all JitsiRemoteTracks associated with given MUC nickname
415
+     * (resource part of the JID). Returns array of removed tracks.
317 416
      *
318
-     * @param {string} resource - The resource part of the MUC JID.
417
+     * @param {string} owner - The resource part of the MUC JID.
319 418
      * @returns {JitsiRemoteTrack[]}
320 419
      */
321
-    removeRemoteTracks (resource) {
322
-        var removedTracks = [];
323
-        var removedAudioTrack = this.removeRemoteTrack(resource, MediaType.AUDIO);
324
-        var removedVideoTrack = this.removeRemoteTrack(resource, MediaType.VIDEO);
420
+    removeRemoteTracks (owner) {
421
+        const removedTracks = [];
325 422
 
326
-        removedAudioTrack && removedTracks.push(removedAudioTrack);
327
-        removedVideoTrack && removedTracks.push(removedVideoTrack);
423
+        if (this.remoteTracks[owner]) {
424
+            const removedAudioTrack
425
+                = this.remoteTracks[owner][MediaType.AUDIO];
426
+            const removedVideoTrack
427
+                = this.remoteTracks[owner][MediaType.VIDEO];
328 428
 
329
-        delete this.remoteTracks[resource];
429
+            removedAudioTrack && removedTracks.push(removedAudioTrack);
430
+            removedVideoTrack && removedTracks.push(removedVideoTrack);
330 431
 
432
+            delete this.remoteTracks[owner];
433
+        }
331 434
         return removedTracks;
332 435
     }
333 436
 
334 437
     /**
335
-     * Removes specified track type associated with given MUC nickname
336
-     * (resource part of the JID). Returns removed track if any.
438
+     * Finds remote track by it's stream and track ids.
439
+     * @param {string} streamId the media stream id as defined by the WebRTC
440
+     * @param {string} trackId the media track id as defined by the WebRTC
441
+     * @return {JitsiRemoteTrack|undefined}
442
+     * @private
443
+     */
444
+    _getRemoteTrackById (streamId, trackId) {
445
+        let result = undefined;
446
+
447
+        // .find will break the loop once the first match is found
448
+        Object.keys(this.remoteTracks).find((endpoint) => {
449
+            const endpointTracks = this.remoteTracks[endpoint];
450
+
451
+            return endpointTracks && Object.keys(endpointTracks).find(
452
+                (mediaType) => {
453
+                    const mediaTrack = endpointTracks[mediaType];
454
+
455
+                    if (mediaTrack
456
+                        && mediaTrack.getStreamId() == streamId
457
+                        && mediaTrack.getTrackId() == trackId) {
458
+                        result = mediaTrack;
459
+                        return true;
460
+                    } else {
461
+                        return false;
462
+                    }
463
+                });
464
+        });
465
+
466
+        return result;
467
+    }
468
+
469
+    /**
470
+     * Removes <tt>JitsiRemoteTrack</tt> identified by given stream and track
471
+     * ids.
337 472
      *
338
-     * @param {string} resource - The resource part of the MUC JID.
339
-     * @param {string} mediaType - Type of track to remove.
340
-     * @returns {JitsiRemoteTrack|undefined}
341
-     */
342
-    removeRemoteTrack (resource, mediaType) {
343
-        var remoteTracksForResource = this.remoteTracks[resource];
344
-
345
-        if (remoteTracksForResource && remoteTracksForResource[mediaType]) {
346
-            var track = remoteTracksForResource[mediaType];
347
-            track.dispose();
348
-            delete remoteTracksForResource[mediaType];
349
-            return track;
473
+     * @param {string} streamId media stream id as defined by the WebRTC
474
+     * @param {string} trackId media track id as defined by the WebRTC
475
+     * @returns {JitsiRemoteTrack|undefined} the track which has been removed or
476
+     * <tt>undefined</tt> if no track matching given stream and track ids was
477
+     * found.
478
+     */
479
+    _removeRemoteTrack (streamId, trackId) {
480
+        const toBeRemoved = this._getRemoteTrackById(streamId, trackId);
481
+
482
+        if (toBeRemoved) {
483
+            toBeRemoved.dispose();
484
+
485
+            delete this.remoteTracks[
486
+                toBeRemoved.getParticipantId()][toBeRemoved.getType()];
487
+
488
+            this.rtc.eventEmitter.emit(
489
+                RTCEvents.REMOTE_TRACK_REMOVED, toBeRemoved);
350 490
         }
491
+
492
+        return toBeRemoved;
351 493
     }
352 494
 
353 495
     static getPCConstraints () {
@@ -427,11 +569,26 @@ export default class RTC extends Listenable {
427 569
      * "streams/channels/tracks" for receiving remote stream/tracks, as opposed
428 570
      * to Plan B where there are only 3 channels: audio, video and data.
429 571
      *
430
-     * @param stream WebRTC MediaStream instance
572
+     * @param {MediaStream} stream the WebRTC MediaStream instance
431 573
      * @returns {boolean}
432 574
      */
433 575
     static isUserStream (stream) {
434
-        var streamId = RTCUtils.getStreamID(stream);
576
+        return RTC.isUserStreamById(RTCUtils.getStreamID(stream));
577
+    }
578
+
579
+    /**
580
+     * Returns <tt>true<tt/> if a WebRTC MediaStream identified by given stream
581
+     * ID is considered a valid "user" stream which means that it's not a
582
+     * "receive only" stream nor a "mixed" JVB stream.
583
+     *
584
+     * Clients that implement Unified Plan, such as Firefox use recvonly
585
+     * "streams/channels/tracks" for receiving remote stream/tracks, as opposed
586
+     * to Plan B where there are only 3 channels: audio, video and data.
587
+     *
588
+     * @param {string} streamId the id of WebRTC MediaStream
589
+     * @returns {boolean}
590
+     */
591
+    static isUserStreamById (streamId) {
435 592
         return (streamId && streamId !== "mixedmslabel"
436 593
             && streamId !== "default");
437 594
     }
@@ -488,32 +645,26 @@ export default class RTC extends Listenable {
488 645
      * @param ssrc the ssrc to check.
489 646
      */
490 647
     getResourceBySSRC (ssrc) {
491
-        if((this.localVideo && ssrc == this.localVideo.getSSRC())
492
-            || (this.localAudio && ssrc == this.localAudio.getSSRC())) {
648
+        if (this.getLocalTracks().find(
649
+                localTrack => { return localTrack.getSSRC() == ssrc; })) {
493 650
             return this.conference.myUserId();
494 651
         }
495 652
 
496
-        var track = this.getRemoteTrackBySSRC(ssrc);
497
-        return track? track.getParticipantId() : null;
653
+        const track = this.getRemoteTrackBySSRC(ssrc);
654
+        return track ? track.getParticipantId() : null;
498 655
     }
499 656
 
500 657
     /**
501 658
      * Searches in remoteTracks for the ssrc and returns the corresponding
502 659
      * track.
503 660
      * @param ssrc the ssrc to check.
661
+     * @return {JitsiRemoteTrack|undefined} return the first remote track that
662
+     * matches given SSRC or <tt>undefined</tt> if no such track was found.
504 663
      */
505 664
     getRemoteTrackBySSRC (ssrc) {
506
-        for (var resource in this.remoteTracks) {
507
-            var track = this.getRemoteAudioTrack(resource);
508
-            if(track && track.getSSRC() == ssrc) {
509
-                return track;
510
-            }
511
-            track = this.getRemoteVideoTrack(resource);
512
-            if(track && track.getSSRC() == ssrc) {
513
-                return track;
514
-            }
515
-        }
516
-        return null;
665
+        return this.getRemoteTracks().find(function (remoteTrack) {
666
+            return ssrc == remoteTrack.getSSRC();
667
+        });
517 668
     }
518 669
 
519 670
     /**

modules/xmpp/TraceablePeerConnection.js → modules/RTC/TraceablePeerConnection.js Visa fil

@@ -1,20 +1,27 @@
1
-/* global mozRTCPeerConnection, webkitRTCPeerConnection */
1
+/* global __filename, mozRTCPeerConnection, webkitRTCPeerConnection,
2
+    RTCPeerConnection, RTCSessionDescription */
2 3
 
3 4
 import { getLogger } from "jitsi-meet-logger";
4
-const logger = getLogger(__filename);
5
-import SdpConsistency from "./SdpConsistency.js";
6
-import RtxModifier from "./RtxModifier.js";
7
-var RTCBrowserType = require("../RTC/RTCBrowserType.js");
8
-var XMPPEvents = require("../../service/xmpp/XMPPEvents");
9
-var transform = require('sdp-transform');
10
-var SDP = require("./SDP");
11
-var SDPUtil = require("./SDPUtil");
5
+import * as GlobalOnErrorHandler from "../util/GlobalOnErrorHandler";
6
+import RTC from "./RTC";
7
+import RTCBrowserType from "./RTCBrowserType.js";
8
+import RTCEvents from "../../service/RTC/RTCEvents";
9
+import RtxModifier from "../xmpp/RtxModifier.js";
10
+// FIXME SDP tools should end up in some kind of util module
11
+import SDP from "../xmpp/SDP";
12
+import SdpConsistency from "../xmpp/SdpConsistency.js";
13
+import SDPUtil from "../xmpp/SDPUtil";
14
+import transform from "sdp-transform";
12 15
 
13
-var SIMULCAST_LAYERS = 3;
16
+const logger = getLogger(__filename);
17
+const SIMULCAST_LAYERS = 3;
14 18
 
15 19
 /**
16 20
  * Creates new instance of 'TraceablePeerConnection'.
17 21
  *
22
+ * @param {RTC} rtc the instance of <tt>RTC</tt> service
23
+ * @param {number} id the peer connection id assigned by the parent RTC module.
24
+ * @param {SignalingLayer} signalingLayer the signaling layer instance
18 25
  * @param {object} ice_config WebRTC 'PeerConnection' ICE config
19 26
  * @param {object} constraints WebRTC 'PeerConnection' constraints
20 27
  * @param {object} options <tt>TracablePeerConnection</tt> config options.
@@ -23,14 +30,33 @@ var SIMULCAST_LAYERS = 3;
23 30
  * @param {boolean} options.disableRtx if set to 'true' will disable the RTX
24 31
  * @param {boolean} options.preferH264 if set to 'true' H264 will be preferred
25 32
  * over other video codecs.
26
- * @param {EventEmitter} eventEmitter the emitter which wil be used by the new
27
- * instance to emit events.
33
+ *
34
+ * FIXME: initially the purpose of TraceablePeerConnection was to be able to
35
+ * debug the peer connection. Since many other responsibilities have been added
36
+ * it would make sense to extract a separate class from it and come up with
37
+ * a more suitable name.
28 38
  *
29 39
  * @constructor
30 40
  */
31
-function TraceablePeerConnection(ice_config,
32
-                                 constraints, options, eventEmitter) {
41
+function TraceablePeerConnection(rtc, id, signalingLayer, ice_config,
42
+                                 constraints, options) {
33 43
     var self = this;
44
+    /**
45
+     * The parent instance of RTC service which created this
46
+     * <tt>TracablePeerConnection</tt>.
47
+     * @type {RTC}
48
+     */
49
+    this.rtc = rtc;
50
+    /**
51
+     * The peer connection identifier assigned by the RTC module.
52
+     * @type {number}
53
+     */
54
+    this.id = id;
55
+    /**
56
+     * The signaling layer which operates this peer connection.
57
+     * @type {SignalingLayer}
58
+     */
59
+    this.signalingLayer = signalingLayer;
34 60
     this.options = options;
35 61
     var RTCPeerConnectionType = null;
36 62
     if (RTCBrowserType.isFirefox()) {
@@ -44,15 +70,22 @@ function TraceablePeerConnection(ice_config,
44 70
     this.updateLog = [];
45 71
     this.stats = {};
46 72
     this.statsinterval = null;
47
-    this.maxstats = 0; // limit to 300 values, i.e. 5 minutes; set to 0 to disable
73
+    /**
74
+     * @type {number}
75
+     */
76
+    this.maxstats = 0;
48 77
     var Interop = require('sdp-interop').Interop;
49 78
     this.interop = new Interop();
50 79
     var Simulcast = require('sdp-simulcast');
51 80
     this.simulcast = new Simulcast({numOfLayers: SIMULCAST_LAYERS,
52 81
         explodeRemoteSimulcast: false});
53 82
     this.sdpConsistency = new SdpConsistency();
83
+    /**
84
+     * TracablePeerConnection uses RTC's eventEmitter
85
+     * @type {EventEmitter}
86
+     */
87
+    this.eventEmitter = rtc.eventEmitter;
54 88
     this.rtxModifier = new RtxModifier();
55
-    this.eventEmitter = eventEmitter;
56 89
 
57 90
     // override as desired
58 91
     this.trace = function (what, info) {
@@ -74,8 +107,12 @@ function TraceablePeerConnection(ice_config,
74 107
     this.onicecandidate = null;
75 108
     this.peerconnection.onicecandidate = function (event) {
76 109
         // FIXME: this causes stack overflow with Temasys Plugin
77
-        if (!RTCBrowserType.isTemasysPluginUsed())
78
-            self.trace('onicecandidate', JSON.stringify(event.candidate, null, ' '));
110
+        if (!RTCBrowserType.isTemasysPluginUsed()) {
111
+            self.trace(
112
+                'onicecandidate',
113
+                JSON.stringify(event.candidate, null, ' '));
114
+        }
115
+
79 116
         if (self.onicecandidate !== null) {
80 117
             self.onicecandidate(event);
81 118
         }
@@ -94,6 +131,12 @@ function TraceablePeerConnection(ice_config,
94 131
             self.onremovestream(event);
95 132
         }
96 133
     };
134
+    this.peerconnection.onaddstream = function (event) {
135
+        self._remoteStreamAdded(event.stream);
136
+    };
137
+    this.peerconnection.onremovestream = function (event) {
138
+        self._remoteStreamRemoved(event.stream);
139
+    };
97 140
     this.onsignalingstatechange = null;
98 141
     this.peerconnection.onsignalingstatechange = function (event) {
99 142
         self.trace('onsignalingstatechange', self.signalingState);
@@ -165,6 +208,196 @@ var dumpSDP = function(description) {
165 208
     return 'type: ' + description.type + '\r\n' + description.sdp;
166 209
 };
167 210
 
211
+/**
212
+ * Called when new remote MediaStream is added to the PeerConnection.
213
+ * @param {MediaStream} stream the WebRTC MediaStream for remote participant
214
+ */
215
+TraceablePeerConnection.prototype._remoteStreamAdded = function (stream) {
216
+    if (!RTC.isUserStream(stream)) {
217
+        logger.info(
218
+            "Ignored remote 'stream added' event for non-user stream", stream);
219
+        return;
220
+    }
221
+    // Bind 'addtrack'/'removetrack' event handlers
222
+    if (RTCBrowserType.isChrome() || RTCBrowserType.isNWJS()
223
+        || RTCBrowserType.isElectron()) {
224
+        stream.onaddtrack = (event) => {
225
+            this._remoteTrackAdded(event.target, event.track);
226
+        };
227
+        stream.onremovetrack = (event) => {
228
+            this._remoteTrackRemoved(event.target, event.track);
229
+        };
230
+    }
231
+    // Call remoteTrackAdded for each track in the stream
232
+    const streamAudioTracks = stream.getAudioTracks();
233
+    for (const audioTrack of streamAudioTracks) {
234
+        this._remoteTrackAdded(stream, audioTrack);
235
+    }
236
+    const streamVideoTracks = stream.getVideoTracks();
237
+    for (const videoTrack of streamVideoTracks) {
238
+        this._remoteTrackAdded(stream, videoTrack);
239
+    }
240
+};
241
+
242
+
243
+/**
244
+ * Called on "track added" and "stream added" PeerConnection events (because we
245
+ * handle streams on per track basis). Finds the owner and the SSRC for
246
+ * the track and passes that to ChatRoom for further processing.
247
+ * @param {MediaStream} stream the WebRTC MediaStream instance which is
248
+ * the parent of the track
249
+ * @param {MediaStreamTrack} track the WebRTC MediaStreamTrack added for remote
250
+ * participant
251
+ */
252
+TraceablePeerConnection.prototype._remoteTrackAdded = function (stream, track) {
253
+    const streamId = RTC.getStreamID(stream);
254
+    const mediaType = track.kind;
255
+
256
+    logger.info("Remote track added", streamId, mediaType);
257
+
258
+    // look up an associated JID for a stream id
259
+    if (!mediaType) {
260
+        GlobalOnErrorHandler.callErrorHandler(
261
+            new Error(
262
+                `MediaType undefined for remote track, stream id: ${streamId}`
263
+            ));
264
+        // Abort
265
+        return;
266
+    }
267
+
268
+    const remoteSDP = new SDP(this.remoteDescription.sdp);
269
+    const mediaLines = remoteSDP.media.filter(
270
+        function (mediaLines){
271
+            return mediaLines.startsWith("m=" + mediaType);
272
+        });
273
+    if (!mediaLines.length) {
274
+        GlobalOnErrorHandler.callErrorHandler(
275
+            new Error(
276
+                "No media lines for type " + mediaType
277
+                    + " found in remote SDP for remote track: " + streamId));
278
+        // Abort
279
+        return;
280
+    }
281
+
282
+    let ssrcLines = SDPUtil.find_lines(mediaLines[0], 'a=ssrc:');
283
+
284
+    ssrcLines = ssrcLines.filter(
285
+        function (line) {
286
+            const msid
287
+                = RTCBrowserType.isTemasysPluginUsed() ? 'mslabel' : 'msid';
288
+            return line.indexOf(msid + ':' + streamId) !== -1;
289
+        });
290
+    if (!ssrcLines.length) {
291
+        GlobalOnErrorHandler.callErrorHandler(
292
+            new Error(
293
+                "No SSRC lines for streamId " + streamId
294
+                    + " for remote track, media type: " + mediaType));
295
+        // Abort
296
+        return;
297
+    }
298
+
299
+    // FIXME the length of ssrcLines[0] not verified, but it will fail
300
+    // with global error handler anyway
301
+    let trackSsrc = ssrcLines[0].substring(7).split(' ')[0];
302
+    const ownerEndpointId = this.signalingLayer.getSSRCOwner(trackSsrc);
303
+
304
+    if (!ownerEndpointId) {
305
+        GlobalOnErrorHandler.callErrorHandler(
306
+            new Error(
307
+                "No SSRC owner known for: " + trackSsrc
308
+                    + " for remote track, msid: " + streamId
309
+                    + " media type: " + mediaType));
310
+        // Abort
311
+        return;
312
+    }
313
+
314
+    logger.log('associated ssrc', ownerEndpointId, trackSsrc);
315
+
316
+    const peerMediaInfo
317
+        = this.signalingLayer.getPeerMediaInfo(ownerEndpointId, mediaType);
318
+
319
+    if (!peerMediaInfo) {
320
+        GlobalOnErrorHandler.callErrorHandler(
321
+            new Error("No peer media info available for: " + ownerEndpointId));
322
+        // Abort
323
+        return;
324
+    }
325
+
326
+    const muted = peerMediaInfo.muted;
327
+    const videoType = peerMediaInfo.videoType; // can be undefined
328
+
329
+    this.rtc._createRemoteTrack(
330
+        ownerEndpointId, stream, track, mediaType, videoType, trackSsrc, muted);
331
+};
332
+
333
+/**
334
+ * Handles remote stream removal.
335
+ * @param stream the WebRTC MediaStream object which is being removed from the
336
+ * PeerConnection
337
+ */
338
+TraceablePeerConnection.prototype._remoteStreamRemoved = function (stream) {
339
+    if (!RTC.isUserStream(stream)) {
340
+        const id = RTC.getStreamID(stream);
341
+        logger.info(
342
+            `Ignored remote 'stream removed' event for non-user stream ${id}`);
343
+        return;
344
+    }
345
+    // Call remoteTrackRemoved for each track in the stream
346
+    const streamVideoTracks = stream.getVideoTracks();
347
+    for (const videoTrack of streamVideoTracks) {
348
+        this._remoteTrackRemoved(stream, videoTrack);
349
+    }
350
+    const streamAudioTracks = stream.getAudioTracks();
351
+    for (const audioTrack of streamAudioTracks) {
352
+        this._remoteTrackRemoved(stream, audioTrack);
353
+    }
354
+};
355
+
356
+/**
357
+ * Handles remote media track removal.
358
+ * @param {MediaStream} stream WebRTC MediaStream instance which is the parent
359
+ * of the track.
360
+ * @param {MediaStreamTrack} track the WebRTC MediaStreamTrack which has been
361
+ * removed from the PeerConnection.
362
+ */
363
+TraceablePeerConnection.prototype._remoteTrackRemoved
364
+= function (stream, track) {
365
+    const streamId = RTC.getStreamID(stream);
366
+    const trackId = track && track.id;
367
+
368
+    logger.info("Remote track removed", streamId, trackId);
369
+
370
+    if (!streamId) {
371
+        GlobalOnErrorHandler.callErrorHandler(
372
+            new Error("Remote track removal failed - no stream ID"));
373
+        // Abort
374
+        return;
375
+    }
376
+
377
+    if (!trackId) {
378
+        GlobalOnErrorHandler.callErrorHandler(
379
+            new Error("Remote track removal failed - no track ID"));
380
+        // Abort
381
+        return;
382
+    }
383
+
384
+    if (!this.rtc._removeRemoteTrack(streamId, trackId)) {
385
+        // NOTE this warning is always printed when user leaves the room,
386
+        // because we remove remote tracks manually on MUC member left event,
387
+        // before the SSRCs are removed by Jicofo. In most cases it is fine to
388
+        // ignore this warning, but still it's better to keep it printed for
389
+        // debugging purposes.
390
+        //
391
+        // We could change the behaviour to emit track removed only from here,
392
+        // but the order of the events will change and consuming apps could
393
+        // behave unexpectedly (the "user left" event would come before "track
394
+        // removed" events).
395
+        logger.warn(
396
+            `Removed track not found for msid: ${streamId},
397
+             track id: ${trackId}`);
398
+    }
399
+};
400
+
168 401
 /**
169 402
  * Returns map with keys msid and values ssrc.
170 403
  * @param desc the SDP that will be modified.
@@ -259,7 +492,7 @@ var normalizePlanB = function(desc) {
259 492
                 });
260 493
             }
261 494
 
262
-            if (typeof mLine.ssrcs !== 'undefined' && Array.isArray(mLine.ssrcs)) {
495
+            if (Array.isArray(mLine.ssrcs)) {
263 496
                 var i;
264 497
                 for (i = 0; i<mLine.ssrcs.length; i++){
265 498
                     if (typeof mLine.ssrcs[i] === 'object'
@@ -315,7 +548,8 @@ var getters = {
315 548
         // if we're running on FF, transform to Plan B first.
316 549
         if (RTCBrowserType.usesUnifiedPlan()) {
317 550
             desc = this.interop.toPlanB(desc);
318
-            this.trace('getRemoteDescription::postTransform (Plan B)', dumpSDP(desc));
551
+            this.trace(
552
+                'getRemoteDescription::postTransform (Plan B)', dumpSDP(desc));
319 553
         }
320 554
         return desc;
321 555
     }
@@ -392,7 +626,8 @@ TraceablePeerConnection.prototype.setLocalDescription
392 626
         },
393 627
         function (err) {
394 628
             self.trace('setLocalDescriptionOnFailure', err);
395
-            self.eventEmitter.emit(XMPPEvents.SET_LOCAL_DESCRIPTION_FAILED,
629
+            self.eventEmitter.emit(
630
+                RTCEvents.SET_LOCAL_DESCRIPTION_FAILED,
396 631
                 err, self.peerconnection);
397 632
             failureCallback(err);
398 633
         }
@@ -404,7 +639,9 @@ TraceablePeerConnection.prototype.setRemoteDescription
404 639
     this.trace('setRemoteDescription::preTransform', dumpSDP(description));
405 640
     // TODO the focus should squeze or explode the remote simulcast
406 641
     description = this.simulcast.mungeRemoteDescription(description);
407
-    this.trace('setRemoteDescription::postTransform (simulcast)', dumpSDP(description));
642
+    this.trace(
643
+        'setRemoteDescription::postTransform (simulcast)',
644
+        dumpSDP(description));
408 645
 
409 646
     if (this.options.preferH264) {
410 647
         const parsedSdp = transform.parse(description.sdp);
@@ -418,7 +655,9 @@ TraceablePeerConnection.prototype.setRemoteDescription
418 655
         description.sdp = this.rtxModifier.stripRtx(description.sdp);
419 656
         this.trace('setRemoteDescription::postTransform (stripRtx)', dumpSDP(description));
420 657
         description = this.interop.toUnifiedPlan(description);
421
-        this.trace('setRemoteDescription::postTransform (Plan A)', dumpSDP(description));
658
+        this.trace(
659
+            'setRemoteDescription::postTransform (Plan A)',
660
+            dumpSDP(description));
422 661
     }
423 662
 
424 663
     if (RTCBrowserType.usesPlanB()) {
@@ -433,7 +672,7 @@ TraceablePeerConnection.prototype.setRemoteDescription
433 672
         },
434 673
         function (err) {
435 674
             self.trace('setRemoteDescriptionOnFailure', err);
436
-            self.eventEmitter.emit(XMPPEvents.SET_REMOTE_DESCRIPTION_FAILED,
675
+            self.eventEmitter.emit(RTCEvents.SET_REMOTE_DESCRIPTION_FAILED,
437 676
                 err, self.peerconnection);
438 677
             failureCallback(err);
439 678
         }
@@ -459,6 +698,9 @@ TraceablePeerConnection.prototype.generateRecvonlySsrc = function() {
459 698
 
460 699
 TraceablePeerConnection.prototype.close = function () {
461 700
     this.trace('stop');
701
+    if (!this.rtc._removePeerConnection(this)) {
702
+        logger.error("RTC._removePeerConnection returned false");
703
+    }
462 704
     if (this.statsinterval !== null) {
463 705
         window.clearInterval(this.statsinterval);
464 706
         this.statsinterval = null;
@@ -578,7 +820,7 @@ TraceablePeerConnection.prototype.createAnswer
578 820
                 _fixAnswerRFC4145Setup(remoteDescription, localDescription);
579 821
                 answer.sdp = localDescription.raw;
580 822
 
581
-                this.eventEmitter.emit(XMPPEvents.SENDRECV_STREAMS_CHANGED,
823
+                this.eventEmitter.emit(RTCEvents.SENDRECV_STREAMS_CHANGED,
582 824
                     extractSSRCMap(answer));
583 825
 
584 826
                 successCallback(answer);
@@ -591,7 +833,7 @@ TraceablePeerConnection.prototype.createAnswer
591 833
         },
592 834
         (err) => {
593 835
             this.trace('createAnswerOnFailure', err);
594
-            this.eventEmitter.emit(XMPPEvents.CREATE_ANSWER_FAILED, err,
836
+            this.eventEmitter.emit(RTCEvents.CREATE_ANSWER_FAILED, err,
595 837
                 this.peerconnection);
596 838
             failureCallback(err);
597 839
         },

+ 3
- 6
modules/connectivity/ConnectionQuality.js Visa fil

@@ -5,7 +5,6 @@ import {getLogger} from "jitsi-meet-logger";
5 5
 import RTCBrowserType from "../RTC/RTCBrowserType";
6 6
 
7 7
 var XMPPEvents = require('../../service/xmpp/XMPPEvents');
8
-var MediaType = require('../../service/RTC/MediaType');
9 8
 var VideoType = require('../../service/RTC/VideoType');
10 9
 var Resolutions = require("../../service/RTC/Resolutions");
11 10
 
@@ -359,8 +358,7 @@ export default class ConnectionQuality {
359 358
         // about the resolution, and they look at their local rendered
360 359
         // resolution instead. Consider removing this.
361 360
         let localVideoTrack
362
-            = this._conference.getLocalTracks(MediaType.VIDEO)
363
-                .find(track => track.isVideoTrack());
361
+            = this._conference.getLocalVideoTrack();
364 362
         if (localVideoTrack && localVideoTrack.resolution) {
365 363
             data.resolution = localVideoTrack.resolution;
366 364
         }
@@ -390,9 +388,8 @@ export default class ConnectionQuality {
390 388
         let key;
391 389
         let updateLocalConnectionQuality
392 390
             = !this._conference.isConnectionInterrupted();
393
-        let localVideoTrack =
394
-                this._conference.getLocalTracks(MediaType.VIDEO)
395
-                    .find(track => track.isVideoTrack());
391
+        let localVideoTrack
392
+            = this._conference.getLocalVideoTrack();
396 393
         let videoType = localVideoTrack ? localVideoTrack.videoType : undefined;
397 394
         let isMuted = localVideoTrack ? localVideoTrack.isMuted() : true;
398 395
         let resolution = localVideoTrack ? localVideoTrack.resolution : null;

+ 37
- 24
modules/xmpp/ChatRoom.js Visa fil

@@ -746,34 +746,47 @@ export default class ChatRoom extends Listenable {
746 746
         this.sendPresence();
747 747
     }
748 748
 
749
-    remoteTrackAdded (data) {
749
+    /**
750
+     * Obtains the info about given media advertised in the MUC presence of
751
+     * the participant identified by the given endpoint JID.
752
+     * @param {string} endpointId the endpoint ID mapped to the participant
753
+     * which corresponds to MUC nickname.
754
+     * @param {MediaType} mediaType the type of the media for which presence
755
+     * info will be obtained.
756
+     * @return {PeerMediaInfo} presenceInfo an object with media presence
757
+     * info or <tt>null</tt> either if there is no presence available or if
758
+     * the media type given is invalid.
759
+     */
760
+    getMediaPresenceInfo (endpointId, mediaType) {
750 761
         // Will figure out current muted status by looking up owner's presence
751
-        var pres = this.lastPresences[data.owner];
752
-        if(pres) {
753
-            var mediaType = data.mediaType;
754
-            var mutedNode = null;
755
-            if (mediaType === MediaType.AUDIO) {
756
-                mutedNode = filterNodeFromPresenceJSON(pres, "audiomuted");
757
-            } else if (mediaType === MediaType.VIDEO) {
758
-                mutedNode = filterNodeFromPresenceJSON(pres, "videomuted");
759
-                var videoTypeNode = filterNodeFromPresenceJSON(pres, "videoType");
760
-                if(videoTypeNode
761
-                    && videoTypeNode.length > 0
762
-                    && videoTypeNode[0])
763
-                    data.videoType = videoTypeNode[0]["value"];
764
-            } else {
765
-                logger.warn("Unsupported media type: " + mediaType);
766
-                data.muted = null;
767
-            }
768
-
769
-            if (mutedNode) {
770
-                data.muted = mutedNode.length > 0 &&
771
-                             mutedNode[0] &&
772
-                             mutedNode[0]["value"] === "true";
762
+        const pres = this.lastPresences[this.roomjid + "/" + endpointId];
763
+        if (!pres) {
764
+            // No presence available
765
+            return null;
766
+        }
767
+        const data = {
768
+            muted: false, // unmuted by default
769
+            videoType: undefined // no video type by default
770
+        };
771
+        let mutedNode = null;
772
+
773
+        if (mediaType === MediaType.AUDIO) {
774
+            mutedNode = filterNodeFromPresenceJSON(pres, "audiomuted");
775
+        } else if (mediaType === MediaType.VIDEO) {
776
+            mutedNode = filterNodeFromPresenceJSON(pres, "videomuted");
777
+            let videoTypeNode = filterNodeFromPresenceJSON(pres, "videoType");
778
+
779
+            if(videoTypeNode.length > 0) {
780
+                data.videoType = videoTypeNode[0]["value"];
773 781
             }
782
+        } else {
783
+            logger.error("Unsupported media type: " + mediaType);
784
+            return null;
774 785
         }
775 786
 
776
-        this.eventEmitter.emit(XMPPEvents.REMOTE_TRACK_ADDED, data);
787
+        data.muted = mutedNode.length > 0 && mutedNode[0]["value"] === "true";
788
+
789
+        return data;
777 790
     }
778 791
 
779 792
     /**

+ 9
- 1
modules/xmpp/JingleSession.js Visa fil

@@ -56,14 +56,21 @@ function JingleSession(me, sid, peerjid, connection,
56 56
      * @type {JingleSessionState}
57 57
      */
58 58
     this.state = null;
59
+
60
+    /**
61
+     * The RTC service instance
62
+     * @type {RTC}
63
+     */
64
+    this.rtc = null;
59 65
 }
60 66
 
61 67
 /**
62 68
  * Prepares this object to initiate a session.
63 69
  * @param isInitiator whether we will be the Jingle initiator.
64 70
  * @param room <tt>ChatRoom<tt> for the conference associated with this session
71
+ * @param {RTC} rtc the RTC service instance
65 72
  */
66
-JingleSession.prototype.initialize = function(isInitiator, room) {
73
+JingleSession.prototype.initialize = function(isInitiator, room, rtc) {
67 74
     if (this.state !== null) {
68 75
         var errmsg
69 76
             = 'attempt to initiate on session ' + this.sid + 'in state '
@@ -72,6 +79,7 @@ JingleSession.prototype.initialize = function(isInitiator, room) {
72 79
         throw new Error(errmsg);
73 80
     }
74 81
     this.room = room;
82
+    this.rtc = rtc;
75 83
     this.state = JingleSessionState.PENDING;
76 84
     this.initiator = isInitiator ? this.me : this.peerjid;
77 85
     this.responder = !isInitiator ? this.me : this.peerjid;

+ 20
- 159
modules/xmpp/JingleSessionPC.js Visa fil

@@ -1,16 +1,14 @@
1
-/* global $, $iq */
1
+/* global $, $iq, Strophe */
2 2
 
3 3
 import {getLogger} from "jitsi-meet-logger";
4 4
 const logger = getLogger(__filename);
5 5
 var JingleSession = require("./JingleSession");
6
-var TraceablePeerConnection = require("./TraceablePeerConnection");
7 6
 var SDPDiffer = require("./SDPDiffer");
8 7
 var SDPUtil = require("./SDPUtil");
9 8
 var SDP = require("./SDP");
10 9
 var async = require("async");
11 10
 var XMPPEvents = require("../../service/xmpp/XMPPEvents");
12 11
 var RTCBrowserType = require("../RTC/RTCBrowserType");
13
-import RTC from "../RTC/RTC";
14 12
 var GlobalOnErrorHandler = require("../util/GlobalOnErrorHandler");
15 13
 var Statistics = require("../statistics/statistics");
16 14
 
@@ -42,6 +40,10 @@ var IQ_TIMEOUT = 10000;
42 40
  * candidates.
43 41
  * @param {boolean} options.failICE it's an option used in the tests. Set to
44 42
  * <tt>true</tt> to block any real candidates and make the ICE fail.
43
+ *
44
+ * @constructor
45
+ *
46
+ * @implements {SignalingLayer}
45 47
  */
46 48
 function JingleSessionPC(me, sid, peerjid, connection,
47 49
                          media_constraints, ice_config, options) {
@@ -94,7 +96,6 @@ function JingleSessionPC(me, sid, peerjid, connection,
94 96
 JingleSessionPC.prototype = Object.create(JingleSession.prototype);
95 97
 JingleSessionPC.prototype.constructor = JingleSessionPC;
96 98
 
97
-
98 99
 JingleSessionPC.prototype.doInitialize = function () {
99 100
     var self = this;
100 101
     this.lasticecandidate = false;
@@ -102,21 +103,16 @@ JingleSessionPC.prototype.doInitialize = function () {
102 103
     this.isreconnect = false;
103 104
     // Set to true if the connection was ever stable
104 105
     this.wasstable = false;
105
-
106
-    this.peerconnection = new TraceablePeerConnection(
106
+    // Create new peer connection instance
107
+    this.peerconnection = this.rtc.createPeerConnection(
108
+        this,
107 109
         this.connection.jingle.ice_config,
108
-        RTC.getPCConstraints(),
109 110
         /* Options */
110 111
         {
111 112
             disableSimulcast: this.room.options.disableSimulcast,
112 113
             disableRtx: this.room.options.disableRtx,
113 114
             preferH264: this.room.options.preferH264
114
-        },
115
-        // TPC is using room's eventEmitter, so that all XMPPEvents can be
116
-        // captured from ChatRoom. But at the same time it makes hard
117
-        // or impossible to deal with more than one TPC instance without
118
-        // further refactoring.
119
-        this.room.eventEmitter);
115
+        });
120 116
 
121 117
     this.peerconnection.onicecandidate = function (ev) {
122 118
         if (!ev) {
@@ -145,12 +141,6 @@ JingleSessionPC.prototype.doInitialize = function () {
145 141
         }
146 142
         self.sendIceCandidate(candidate);
147 143
     };
148
-    this.peerconnection.onaddstream = function (event) {
149
-        self.remoteStreamAdded(event.stream);
150
-    };
151
-    this.peerconnection.onremovestream = function (event) {
152
-        self.remoteStreamRemoved(event.stream);
153
-    };
154 144
     // Note there is a change in the spec about closed:
155 145
     // This value moved into the RTCPeerConnectionState enum in the May 13, 2016
156 146
     // draft of the specification, as it reflects the state of the
@@ -308,8 +298,11 @@ JingleSessionPC.prototype.readSsrcInfo = function (contents) {
308 298
             var ssrc = this.getAttribute('ssrc');
309 299
             $(this).find('>ssrc-info[xmlns="http://jitsi.org/jitmeet"]').each(
310 300
                 function () {
311
-                    var owner = this.getAttribute('owner');
312
-                    self.ssrcOwners[ssrc] = owner;
301
+                    const owner = this.getAttribute('owner');
302
+                    if (owner && owner.length) {
303
+                        self.ssrcOwners[ssrc]
304
+                            = Strophe.getResourceFromJid(owner);
305
+                    }
313 306
                 }
314 307
             );
315 308
         });
@@ -1278,149 +1271,17 @@ JingleSessionPC.onJingleFatalError = function (session, error)
1278 1271
 };
1279 1272
 
1280 1273
 /**
1281
- * Called when new remote MediaStream is added to the PeerConnection.
1282
- * @param stream the WebRTC MediaStream for remote participant
1283
- */
1284
-JingleSessionPC.prototype.remoteStreamAdded = function (stream) {
1285
-    var self = this;
1286
-    if (!RTC.isUserStream(stream)) {
1287
-        logger.info(
1288
-            "Ignored remote 'stream added' event for non-user stream", stream);
1289
-        return;
1290
-    }
1291
-    // Bind 'addtrack'/'removetrack' event handlers
1292
-    if (RTCBrowserType.isChrome() || RTCBrowserType.isNWJS()
1293
-        || RTCBrowserType.isElectron()) {
1294
-        stream.onaddtrack = function (event) {
1295
-            self.remoteTrackAdded(event.target, event.track);
1296
-        };
1297
-        stream.onremovetrack = function (event) {
1298
-            self.remoteTrackRemoved(event.target, event.track);
1299
-        };
1300
-    }
1301
-    // Call remoteTrackAdded for each track in the stream
1302
-    stream.getAudioTracks().forEach(function (track) {
1303
-        self.remoteTrackAdded(stream, track);
1304
-    });
1305
-    stream.getVideoTracks().forEach(function (track) {
1306
-        self.remoteTrackAdded(stream, track);
1307
-    });
1308
-};
1309
-
1310
-/**
1311
- * Called on "track added" and "stream added" PeerConnection events(cause we
1312
- * handle streams on per track basis). Does find the owner and the SSRC for
1313
- * the track and passes that to ChatRoom for further processing.
1314
- * @param stream WebRTC MediaStream instance which is the parent of the track
1315
- * @param track the WebRTC MediaStreamTrack added for remote participant
1316
- */
1317
-JingleSessionPC.prototype.remoteTrackAdded = function (stream, track) {
1318
-    logger.info("Remote track added", stream, track);
1319
-    var streamId = RTC.getStreamID(stream);
1320
-    var mediaType = track.kind;
1321
-
1322
-    // This is our event structure which will be passed by the ChatRoom as
1323
-    // XMPPEvents.REMOTE_TRACK_ADDED data
1324
-    var jitsiTrackAddedEvent = {
1325
-        stream: stream,
1326
-        track: track,
1327
-        mediaType: track.kind, /* 'audio' or 'video' */
1328
-        owner: undefined, /* to be determined below */
1329
-        muted: null /* will be set in the ChatRoom */
1330
-    };
1331
-    try{
1332
-        // look up an associated JID for a stream id
1333
-        if (!mediaType) {
1334
-            logger.error("MediaType undefined", track);
1335
-            throw new Error("MediaType undefined for remote track");
1336
-        }
1337
-
1338
-        var remoteSDP = new SDP(this.peerconnection.remoteDescription.sdp);
1339
-        var medialines = remoteSDP.media.filter(function (mediaLines){
1340
-            return mediaLines.startsWith("m=" + mediaType);
1341
-        });
1342
-        if (!medialines.length) {
1343
-            logger.error("No media for type " + mediaType + " found in remote SDP");
1344
-            throw new Error("No media for type " + mediaType +
1345
-                " found in remote SDP for remote track");
1346
-        }
1347
-
1348
-        var ssrclines = SDPUtil.find_lines(medialines[0], 'a=ssrc:');
1349
-        ssrclines = ssrclines.filter(function (line) {
1350
-            var msid = RTCBrowserType.isTemasysPluginUsed() ? 'mslabel' : 'msid';
1351
-            return line.indexOf(msid + ':' + streamId) !== -1;
1352
-        });
1353
-
1354
-        var thessrc;
1355
-        if (ssrclines.length) {
1356
-            thessrc = ssrclines[0].substring(7).split(' ')[0];
1357
-            if (!this.ssrcOwners[thessrc]) {
1358
-                logger.error("No SSRC owner known for: " + thessrc);
1359
-                throw new Error("No SSRC owner known for: " + thessrc +
1360
-                    " for remote track");
1361
-            }
1362
-            jitsiTrackAddedEvent.owner = this.ssrcOwners[thessrc];
1363
-            logger.log('associated jid', this.ssrcOwners[thessrc], thessrc);
1364
-        } else {
1365
-            logger.error("No SSRC lines for ", streamId);
1366
-            throw new Error("No SSRC lines for streamId " + streamId +
1367
-                " for remote track");
1368
-        }
1369
-        jitsiTrackAddedEvent.ssrc = thessrc;
1370
-
1371
-        this.room.remoteTrackAdded(jitsiTrackAddedEvent);
1372
-    } catch (error) {
1373
-        GlobalOnErrorHandler.callErrorHandler(error);
1374
-    }
1375
-};
1376
-
1377
-/**
1378
- * Handles remote stream removal.
1379
- * @param stream the WebRTC MediaStream object which is being removed from the
1380
- * PeerConnection
1274
+ * @inheritDoc
1381 1275
  */
1382
-JingleSessionPC.prototype.remoteStreamRemoved = function (stream) {
1383
-    var self = this;
1384
-    if (!RTC.isUserStream(stream)) {
1385
-        logger.info(
1386
-            "Ignored remote 'stream removed' event for non-user stream", stream);
1387
-        return;
1388
-    }
1389
-    // Call remoteTrackRemoved for each track in the stream
1390
-    stream.getVideoTracks().forEach(function(track){
1391
-        self.remoteTrackRemoved(stream, track);
1392
-    });
1393
-    stream.getAudioTracks().forEach(function(track) {
1394
-       self.remoteTrackRemoved(stream, track);
1395
-    });
1276
+JingleSessionPC.prototype.getPeerMediaInfo  = function (owner, mediaType) {
1277
+    return this.room.getMediaPresenceInfo(owner, mediaType);
1396 1278
 };
1397 1279
 
1398 1280
 /**
1399
- * Handles remote media track removal.
1400
- * @param stream WebRTC MediaStream instance which is the parent of the track
1401
- * @param track the WebRTC MediaStreamTrack which has been removed from
1402
- * the PeerConnection.
1281
+ * @inheritDoc
1403 1282
  */
1404
-JingleSessionPC.prototype.remoteTrackRemoved = function (stream, track) {
1405
-    logger.info("Remote track removed", stream, track);
1406
-    var streamId = RTC.getStreamID(stream);
1407
-    var trackId = track && track.id;
1408
-    try{
1409
-        if (!streamId) {
1410
-            logger.error("No stream ID for", stream);
1411
-            throw new Error("Remote track removal failed - No stream ID");
1412
-        }
1413
-
1414
-        if (!trackId) {
1415
-            logger.error("No track ID for", track);
1416
-            throw new Error("Remote track removal failed - No track ID");
1417
-        }
1418
-
1419
-        this.room.eventEmitter.emit(
1420
-            XMPPEvents.REMOTE_TRACK_REMOVED, streamId, trackId);
1421
-    } catch (error) {
1422
-        GlobalOnErrorHandler.callErrorHandler(error);
1423
-    }
1283
+JingleSessionPC.prototype.getSSRCOwner = function(ssrc) {
1284
+    return this.ssrcOwners[ssrc];
1424 1285
 };
1425 1286
 
1426 1287
 /**

+ 8
- 6
modules/xmpp/SDP.js Visa fil

@@ -251,12 +251,14 @@ SDP.prototype.toJingle = function (elem, thecreator) {
251 251
                     elem.c('parameter');
252 252
                     elem.attrs({name: "cname", value:Math.random().toString(36).substring(7)});
253 253
                     elem.up();
254
-                    var msid = null;
255
-                    if(mline.media == "audio") {
256
-                        // FIXME what is this ? global APP.RTC in SDP ?
257
-                        msid = APP.RTC.localAudio._getId();
258
-                    } else {
259
-                        msid = APP.RTC.localVideo._getId();
254
+                    // FIXME what case does this code handle ? remove ???
255
+                    let msid = null;
256
+                    // FIXME what is this ? global APP.RTC in SDP ?
257
+                    const localTrack = APP.RTC.getLocalTracks(mline.media);
258
+                    if (localTrack) {
259
+                        // FIXME before this changes the track id was accessed,
260
+                        // but msid stands for the stream id, makes no sense ?
261
+                        msid = localTrack.getTrackId();
260 262
                     }
261 263
                     if(msid != null) {
262 264
                         msid = SDPUtil.filter_special_chars(msid);

+ 41
- 0
service/RTC/RTCEvents.js Visa fil

@@ -1,5 +1,19 @@
1 1
 var RTCEvents = {
2
+    /**
3
+     * Indicates error while create answer call.
4
+     */
5
+    CREATE_ANSWER_FAILED: "rtc.create_answer_failed",
6
+    /**
7
+     * Indicates error while create offer call.
8
+     * FIXME not used (yet), but hook up with create offer failure once added
9
+     */
10
+    CREATE_OFFER_FAILED: "rtc.create_offer_failed",
2 11
     RTC_READY: "rtc.ready",
12
+    /**
13
+     * FIXME: rename to something closer to "local streams SDP changed"
14
+     * Indicates that the local sendrecv streams in local SDP are changed.
15
+     */
16
+    SENDRECV_STREAMS_CHANGED: "rtc.sendrecv_streams_changed",
3 17
     DATA_CHANNEL_OPEN: "rtc.data_channel_open",
4 18
     ENDPOINT_CONN_STATUS_CHANGED: "rtc.endpoint_conn_status_changed",
5 19
     LASTN_CHANGED: "rtc.lastn_changed",
@@ -7,12 +21,39 @@ var RTCEvents = {
7 21
     LASTN_ENDPOINT_CHANGED: "rtc.lastn_endpoint_changed",
8 22
     AVAILABLE_DEVICES_CHANGED: "rtc.available_devices_changed",
9 23
     TRACK_ATTACHED: "rtc.track_attached",
24
+    /**
25
+     * Event fired when we remote track is added to the conference.
26
+     * The following structure is passed as an argument:
27
+     * {
28
+     *   stream: the WebRTC MediaStream instance
29
+     *   track: the WebRTC MediaStreamTrack
30
+     *   mediaType: the MediaType instance
31
+     *   owner: the MUC JID of the stream owner
32
+     *   muted: a boolean indicating initial 'muted' status of the track or
33
+      *         'null' if unknown
34
+     **/
35
+    REMOTE_TRACK_ADDED: "rtc.remote_track_added",
10 36
     // FIXME get rid of this event in favour of NO_DATA_FROM_SOURCE event
11 37
     // (currently implemented for local tracks only)
12 38
     REMOTE_TRACK_MUTE: "rtc.remote_track_mute",
39
+    /**
40
+     * Indicates that the remote track has been removed from the conference.
41
+     * 1st event argument is the ID of the parent WebRTC stream to which
42
+     * the track being removed belongs to.
43
+     * 2nd event argument is the ID of the removed track.
44
+     */
45
+    REMOTE_TRACK_REMOVED: "rtc.remote_track_removed",
13 46
     // FIXME get rid of this event in favour of NO_DATA_FROM_SOURCE event
14 47
     // (currently implemented for local tracks only)
15 48
     REMOTE_TRACK_UNMUTE: "rtc.remote_track_unmute",
49
+    /**
50
+     * Indicates error while set local description.
51
+     */
52
+    SET_LOCAL_DESCRIPTION_FAILED: "rtc.set_local_description_failed",
53
+    /**
54
+     * Indicates error while set remote description.
55
+     */
56
+    SET_REMOTE_DESCRIPTION_FAILED: "rtc.set_remote_description_failed",
16 57
     AUDIO_OUTPUT_DEVICE_CHANGED: "rtc.audio_output_device_changed",
17 58
     DEVICE_LIST_CHANGED: "rtc.device_list_changed",
18 59
     DEVICE_LIST_AVAILABLE: "rtc.device_list_available",

+ 40
- 0
service/RTC/SignalingLayer.js Visa fil

@@ -0,0 +1,40 @@
1
+
2
+/**
3
+ * An object that carries the info about specific media type advertised by
4
+ * participant in the signalling channel.
5
+ * @typedef {Object} PeerMediaInfo
6
+ * @property {boolean} muted indicates if the media is currently muted
7
+ * @property {VideoType|undefined} videoType the type of the video if applicable
8
+ */
9
+
10
+/**
11
+ * Interface used to expose the information carried over the signalling channel
12
+ * which is not available to the RTC module in the media SDP.
13
+ *
14
+ * @interface SignalingLayer
15
+ */
16
+export default class SignalingLayer {
17
+
18
+    /**
19
+     * Obtains the endpoint ID for given SSRC.
20
+     * @param {string} ssrc a string representation of the SSRC number.
21
+     * @return {string|null} the endpoint ID for given media SSRC.
22
+     */
23
+    getSSRCOwner (ssrc) { // eslint-disable-line no-unused-vars
24
+        throw new Error('not implemented');
25
+    }
26
+    /**
27
+     * Obtains the info about given media advertised in the MUC presence of
28
+     * the participant identified by the given MUC JID.
29
+     * @param {string} owner the MUC jid of the participant for whom
30
+     * {@link PeerMediaInfo} will be obtained.
31
+     * @param {MediaType} mediaType the type of the media for which presence
32
+     * info will be obtained.
33
+     * @return {PeerMediaInfo|null} presenceInfo an object with media presence
34
+     * info or <tt>null</tt> either if there is no presence available for given
35
+     * JID or if the media type given is invalid.
36
+     */
37
+    getPeerMediaInfo (owner, mediaType) { // eslint-disable-line no-unused-vars
38
+        throw new Error('not implemented');
39
+    }
40
+}

+ 0
- 46
service/xmpp/XMPPEvents.js Visa fil

@@ -30,16 +30,6 @@ var XMPPEvents = {
30 30
     // Designates an event indicating that the media (ICE) connection failed.
31 31
     // This should go to the RTC module.
32 32
     CONNECTION_ICE_FAILED: "xmpp.connection.ice.failed",
33
-    // TODO: only used in a hack, should probably be removed.
34
-    CREATE_ANSWER_ERROR: 'xmpp.create_answer_error',
35
-    /**
36
-     * Indicates error while create answer call.
37
-     */
38
-    CREATE_ANSWER_FAILED: "xmpp.create_answer_failed",
39
-    /**
40
-     * Indicates error while create offer call.
41
-     */
42
-    CREATE_OFFER_FAILED: "xmpp.create_offer_failed",
43 33
     // Designates an event indicating that the display name of a participant
44 34
     // has changed.
45 35
     DISPLAY_NAME_CHANGED: "xmpp.display_name_changed",
@@ -127,25 +117,6 @@ var XMPPEvents = {
127 117
     // Designates an event indicating that we received statistics from a
128 118
     // participant in the MUC.
129 119
     REMOTE_STATS: "xmpp.remote_stats",
130
-    /**
131
-     * Event fired when we remote track is added to the conference.
132
-     * The following structure is passed as an argument:
133
-     * {
134
-     *   stream: the WebRTC MediaStream instance
135
-     *   track: the WebRTC MediaStreamTrack
136
-     *   mediaType: the MediaType instance
137
-     *   owner: the MUC JID of the stream owner
138
-     *   muted: a boolean indicating initial 'muted' status of the track or
139
-      *         'null' if unknown
140
-     **/
141
-    REMOTE_TRACK_ADDED: "xmpp.remote_track_added",
142
-    /**
143
-     * Indicates that the remote track has been removed from the conference.
144
-     * 1st event argument is the ID of the parent WebRTC stream to which
145
-     * the track being removed belongs to.
146
-     * 2nd event argument is the ID of the removed track.
147
-     */
148
-    REMOTE_TRACK_REMOVED: "xmpp.remote_track_removed",
149 120
     RESERVATION_ERROR: "xmpp.room_reservation_error",
150 121
     ROOM_CONNECT_ERROR: 'xmpp.room_connect_error',
151 122
     ROOM_CONNECT_NOT_ALLOWED_ERROR: 'xmpp.room_connect_error.not_allowed',
@@ -156,10 +127,6 @@ var XMPPEvents = {
156 127
     ROOM_MAX_USERS_ERROR: "xmpp.room_max_users_error",
157 128
     // Designates an event indicating that we sent an XMPP message to the MUC.
158 129
     SENDING_CHAT_MESSAGE: "xmpp.sending_chat_message",
159
-    /**
160
-     * Indicates that the local sendrecv streams in local SDP are changed.
161
-     */
162
-    SENDRECV_STREAMS_CHANGED: "xmpp.sendrecv_streams_changed",
163 130
     /**
164 131
      * Event fired when we do not get our 'session-accept' acknowledged by
165 132
      * Jicofo. It most likely means that there is serious problem with our
@@ -171,19 +138,6 @@ var XMPPEvents = {
171 138
      * packets means that most likely it has never seen our IQ.
172 139
      */
173 140
     SESSION_ACCEPT_TIMEOUT: "xmpp.session_accept_timeout",
174
-    // TODO: only used in a hack, should probably be removed.
175
-    SET_LOCAL_DESCRIPTION_ERROR: 'xmpp.set_local_description_error',
176
-
177
-    /**
178
-     * Indicates error while set local description.
179
-     */
180
-    SET_LOCAL_DESCRIPTION_FAILED: "xmpp.set_local_description_failed",
181
-    // TODO: only used in a hack, should probably be removed.
182
-    SET_REMOTE_DESCRIPTION_ERROR: 'xmpp.set_remote_description_error',
183
-    /**
184
-     * Indicates error while set remote description.
185
-     */
186
-    SET_REMOTE_DESCRIPTION_FAILED: "xmpp.set_remote_description_failed",
187 141
     // Designates an event indicating that we should join the conference with
188 142
     // audio and/or video muted.
189 143
     START_MUTED_FROM_FOCUS: "xmpp.start_muted_from_focus",

Laddar…
Avbryt
Spara