Просмотр исходного кода

Merge pull request #166 from jitsi/reloads

Implements reload functionality for JitsiConnection and JitsiConference
dev1
Дамян Минков 9 лет назад
Родитель
Сommit
47f11a7e39

+ 196
- 487
JitsiConference.js Просмотреть файл

@@ -3,8 +3,6 @@
3 3
 var logger = require("jitsi-meet-logger").getLogger(__filename);
4 4
 var RTC = require("./modules/RTC/RTC");
5 5
 var XMPPEvents = require("./service/xmpp/XMPPEvents");
6
-var AuthenticationEvents = require("./service/authentication/AuthenticationEvents");
7
-var RTCEvents = require("./service/RTC/RTCEvents");
8 6
 var EventEmitter = require("events");
9 7
 var JitsiConferenceEvents = require("./JitsiConferenceEvents");
10 8
 var JitsiConferenceErrors = require("./JitsiConferenceErrors");
@@ -17,6 +15,7 @@ var JitsiTrackError = require("./JitsiTrackError");
17 15
 var Settings = require("./modules/settings/Settings");
18 16
 var ComponentsVersions = require("./modules/version/ComponentsVersions");
19 17
 var GlobalOnErrorHandler = require("./modules/util/GlobalOnErrorHandler");
18
+var JitsiConferenceEventManager = require("./JitsiConferenceEventManager");
20 19
 
21 20
 /**
22 21
  * Creates a JitsiConference object with the given name and properties.
@@ -35,25 +34,13 @@ function JitsiConference(options) {
35 34
         logger.error(errmsg);
36 35
         throw new Error(errmsg);
37 36
     }
38
-    this.options = options;
39
-    this.connection = this.options.connection;
40
-    this.xmpp = this.connection.xmpp;
41 37
     this.eventEmitter = new EventEmitter();
42
-    var confID = this.options.name  + '@' + this.xmpp.options.hosts.muc;
43 38
     this.settings = new Settings();
44
-    this.room = this.xmpp.createRoom(this.options.name, this.options.config,
45
-        this.settings);
46
-    this.componentsVersions = new ComponentsVersions(this.room);
47
-    this.room.updateDeviceAvailability(RTC.getDeviceAvailability());
48
-    this.rtc = new RTC(this.room, options);
49
-    this.statistics = new Statistics(this.xmpp, {
50
-        callStatsID: this.options.config.callStatsID,
51
-        callStatsSecret: this.options.config.callStatsSecret,
52
-        disableThirdPartyRequests:
53
-            this.options.config.disableThirdPartyRequests,
54
-        roomName: this.options.name
55
-    });
56
-    setupListeners(this);
39
+    this.retries = 0;
40
+    this.options = options;
41
+    this.eventManager = new JitsiConferenceEventManager(this);
42
+    this._init(options);
43
+    this.componentsVersions = new ComponentsVersions(this);
57 44
     this.participants = {};
58 45
     this.lastDominantSpeaker = null;
59 46
     this.dtmfManager = null;
@@ -71,6 +58,73 @@ function JitsiConference(options) {
71 58
     this.reportedAudioSSRCs = {};
72 59
 }
73 60
 
61
+/**
62
+ * Initializes the conference object properties
63
+ * @param options {object}
64
+ * @param connection {JitsiConnection} overrides this.connection
65
+ * @param roomState {object} the state of the ChatRoom instance received by
66
+ * ChatRoom.exportState. If this property is set it will be passed to
67
+ * ChatRoom.loadState
68
+ */
69
+JitsiConference.prototype._init = function (options) {
70
+    if(!options)
71
+        options = {};
72
+
73
+    // Override connection and xmpp properties (Usefull if the connection
74
+    // reloaded)
75
+    if(options.connection) {
76
+        this.connection = options.connection;
77
+        this.xmpp = this.connection.xmpp;
78
+        // Setup XMPP events only if we have new connection object.
79
+        this.eventManager.setupXMPPListeners();
80
+    }
81
+
82
+    this.retries++;
83
+    this.room = this.xmpp.createRoom(this.options.name, this.options.config,
84
+        this.settings, (this.retries < 4 ? 3 : null));
85
+
86
+    this.eventManager.setupChatRoomListeners();
87
+
88
+    //restore previous presence options
89
+    if(options.roomState) {
90
+        this.room.loadState(options.roomState);
91
+    }
92
+    this.room.updateDeviceAvailability(RTC.getDeviceAvailability());
93
+
94
+    if(!this.rtc) {
95
+        this.rtc = new RTC(this, options);
96
+        this.eventManager.setupRTCListeners();
97
+    }
98
+
99
+
100
+    if(!this.statistics) {
101
+        this.statistics = new Statistics(this.xmpp, {
102
+            callStatsID: this.options.config.callStatsID,
103
+            callStatsSecret: this.options.config.callStatsSecret,
104
+            disableThirdPartyRequests:
105
+                this.options.config.disableThirdPartyRequests,
106
+            roomName: this.options.name
107
+        });
108
+    }
109
+    // Always add listeners because on reload we are executing leave and the
110
+    // listeners are removed from statistics module.
111
+    this.eventManager.setupStatisticsListeners();
112
+}
113
+
114
+/**
115
+ * Reloads the conference
116
+ * @param options {object} options to be overriden
117
+ */
118
+JitsiConference.prototype._reload = function (options) {
119
+    var roomState = this.room.exportState();
120
+    if(!options)
121
+        options = {};
122
+    options.roomState = roomState;
123
+    this.leave(true);
124
+    this._init(options);
125
+    this.join();
126
+}
127
+
74 128
 /**
75 129
  * Joins the conference.
76 130
  * @param password {string} the password
@@ -91,16 +145,17 @@ JitsiConference.prototype.isJoined = function () {
91 145
  * Leaves the conference and calls onMemberLeft for every participant.
92 146
  */
93 147
 JitsiConference.prototype._leaveRoomAndRemoveParticipants = function () {
148
+    // remove all participants
149
+    this.getParticipants().forEach(function (participant) {
150
+        this.onMemberLeft(participant.getJid());
151
+    }.bind(this));
152
+
94 153
     // leave the conference
95 154
     if (this.room) {
96 155
         this.room.leave();
97 156
     }
98 157
 
99 158
     this.room = null;
100
-    // remove all participants
101
-    this.getParticipants().forEach(function (participant) {
102
-        this.onMemberLeft(participant.getJid());
103
-    }.bind(this));
104 159
 
105 160
     this.eventEmitter.emit(JitsiConferenceEvents.CONFERENCE_LEFT);
106 161
 }
@@ -108,9 +163,16 @@ JitsiConference.prototype._leaveRoomAndRemoveParticipants = function () {
108 163
  * Leaves the conference.
109 164
  * @returns {Promise}
110 165
  */
111
-JitsiConference.prototype.leave = function () {
166
+JitsiConference.prototype.leave = function (dontRemoveLocalTracks) {
112 167
     var conference = this;
113 168
 
169
+    this.statistics.stopCallStats();
170
+    this.rtc.closeAllDataChannels();
171
+    if(dontRemoveLocalTracks) {
172
+        this._leaveRoomAndRemoveParticipants();
173
+        return  Promise.resolve();
174
+    }
175
+
114 176
     return Promise.all(
115 177
         conference.getLocalTracks().map(function (track) {
116 178
             return conference.removeTrack(track);
@@ -369,6 +431,7 @@ JitsiConference.prototype.addTrack = function (track) {
369 431
                 });
370 432
             }
371 433
             this.rtc.addLocalTrack(track);
434
+
372 435
             if (track.startMuted) {
373 436
                 track.mute();
374 437
             }
@@ -386,7 +449,7 @@ JitsiConference.prototype.addTrack = function (track) {
386 449
                                    track.muteHandler);
387 450
             track.addEventListener(JitsiTrackEvents.TRACK_AUDIO_LEVEL_CHANGED,
388 451
                                    track.audioLevelHandler);
389
-            //FIXME: This dependacy is not necessary. This is quick fix.
452
+
390 453
             track._setConference(this);
391 454
 
392 455
             // send event for starting screen sharing
@@ -694,6 +757,84 @@ JitsiConference.prototype.onTrackAdded = function (track) {
694 757
     emitter.emit(JitsiConferenceEvents.TRACK_ADDED, track);
695 758
 };
696 759
 
760
+/**
761
+ * Handles incoming call event.
762
+ */
763
+JitsiConference.prototype.onIncomingCall =
764
+function (jingleSession, jingleOffer, now) {
765
+    if (!this.room.isFocus(jingleSession.peerjid)) {
766
+        // Error cause this should never happen unless something is wrong!
767
+        var errmsg = "Rejecting session-initiate from non-focus user: "
768
+                + jingleSession.peerjid;
769
+        GlobalOnErrorHandler.callErrorHandler(new Error(errmsg));
770
+        logger.error(errmsg);
771
+        return;
772
+    }
773
+
774
+    // Accept incoming call
775
+    this.room.setJingleSession(jingleSession);
776
+    this.room.connectionTimes["session.initiate"] = now;
777
+    try{
778
+        jingleSession.initialize(false /* initiator */,this.room);
779
+    } catch (error) {
780
+        GlobalOnErrorHandler.callErrorHandler(error);
781
+    };
782
+
783
+    this.rtc.onIncommingCall(jingleSession);
784
+    // Add local Tracks to the ChatRoom
785
+    this.rtc.localTracks.forEach(function(localTrack) {
786
+        var ssrcInfo = null;
787
+        if(localTrack.isVideoTrack() && localTrack.isMuted()) {
788
+            /**
789
+             * Handles issues when the stream is added before the peerconnection
790
+             * is created. The peerconnection is created when second participant
791
+             * enters the call. In that use case the track doesn't have
792
+             * information about it's ssrcs and no jingle packets are sent. That
793
+             * can cause inconsistent behavior later.
794
+             *
795
+             * For example:
796
+             * If we mute the stream and than second participant enter it's
797
+             * remote SDP won't include that track. On unmute we are not sending
798
+             * any jingle packets which will brake the unmute.
799
+             *
800
+             * In order to solve issues like the above one here we have to
801
+             * generate the ssrc information for the track .
802
+             */
803
+            localTrack._setSSRC(
804
+                this.room.generateNewStreamSSRCInfo());
805
+            ssrcInfo = {
806
+                mtype: localTrack.getType(),
807
+                type: "addMuted",
808
+                ssrc: localTrack.ssrc,
809
+                msid: localTrack.initialMSID
810
+            };
811
+        }
812
+        try {
813
+            this.room.addStream(
814
+                localTrack.getOriginalStream(), function () {}, function () {},
815
+                ssrcInfo, true);
816
+        } catch(e) {
817
+            GlobalOnErrorHandler.callErrorHandler(e);
818
+            logger.error(e);
819
+        }
820
+    }.bind(this));
821
+
822
+    jingleSession.acceptOffer(jingleOffer, null,
823
+        function (error) {
824
+            GlobalOnErrorHandler.callErrorHandler(error);
825
+            logger.error(
826
+                "Failed to accept incoming Jingle session", error);
827
+        }
828
+    );
829
+
830
+    // Start callstats as soon as peerconnection is initialized,
831
+    // do not wait for XMPPEvents.PEERCONNECTION_READY, as it may never
832
+    // happen in case if user doesn't have or denied permission to
833
+    // both camera and microphone.
834
+    this.statistics.startCallStats(jingleSession, this.settings);
835
+    this.statistics.startRemoteStats(jingleSession.peerconnection);
836
+}
837
+
697 838
 JitsiConference.prototype.updateDTMFSupport = function () {
698 839
     var somebodySupportsDTMF = false;
699 840
     var participants = this.getParticipants();
@@ -947,6 +1088,22 @@ JitsiConference.prototype.isCallstatsEnabled = function () {
947 1088
     return this.statistics.isCallstatsEnabled();
948 1089
 }
949 1090
 
1091
+
1092
+/**
1093
+ * Handles track attached to container (Calls associateStreamWithVideoTag method
1094
+ * from statistics module)
1095
+ * @param track the track
1096
+ * @param container the container
1097
+ */
1098
+JitsiConference.prototype._onTrackAttach = function(track, container) {
1099
+    var ssrc = track.getSSRC();
1100
+    if (!container.id || !ssrc) {
1101
+        return;
1102
+    }
1103
+    this.statistics.associateStreamWithVideoTag(
1104
+        ssrc, track.isLocal(), track.getUsageLabel(), container.id);
1105
+}
1106
+
950 1107
 /**
951 1108
  * Reports detected audio problem with the media stream related to the passed
952 1109
  * ssrc.
@@ -1050,469 +1207,21 @@ JitsiConference.prototype.sendApplicationLog = function(message) {
1050 1207
 };
1051 1208
 
1052 1209
 /**
1053
- * Setups the listeners needed for the conference.
1054
- * @param conference the conference
1210
+ * Checks if the user identified by given <tt>mucJid</tt> is the conference
1211
+ * focus.
1212
+ * @param mucJid the full MUC address of the user to be checked.
1213
+ * @returns {boolean} <tt>true</tt> if MUC user is the conference focus.
1055 1214
  */
1056
-function setupListeners(conference) {
1057
-    conference.xmpp.addListener(
1058
-        XMPPEvents.CALL_INCOMING, function (jingleSession, jingleOffer, now) {
1059
-
1060
-        if (conference.room.isFocus(jingleSession.peerjid)) {
1061
-            // Accept incoming call
1062
-            conference.room.setJingleSession(jingleSession);
1063
-            conference.room.connectionTimes["session.initiate"] = now;
1064
-            try{
1065
-                jingleSession.initialize(false /* initiator */,
1066
-                    conference.room);
1067
-            } catch (error) {
1068
-                GlobalOnErrorHandler.callErrorHandler(error);
1069
-            };
1070
-            conference.rtc.onIncommingCall(jingleSession);
1071
-            jingleSession.acceptOffer(jingleOffer, null,
1072
-                function (error) {
1073
-                    GlobalOnErrorHandler.callErrorHandler(error);
1074
-                    logger.error(
1075
-                        "Failed to accept incoming Jingle session", error);
1076
-                }
1077
-            );
1078
-            // Start callstats as soon as peerconnection is initialized,
1079
-            // do not wait for XMPPEvents.PEERCONNECTION_READY, as it may never
1080
-            // happen in case if user doesn't have or denied permission to
1081
-            // both camera and microphone.
1082
-            conference.statistics.startCallStats(jingleSession, conference.settings);
1083
-            conference.statistics.startRemoteStats(jingleSession.peerconnection);
1084
-        } else {
1085
-            // Error cause this should never happen unless something is wrong!
1086
-            var errmsg
1087
-                = "Rejecting session-initiate from non-focus user: "
1088
-                    + jingleSession.peerjid;
1089
-            GlobalOnErrorHandler.callErrorHandler(new Error(errmsg));
1090
-            logger.error(errmsg);
1091
-        }
1092
-    });
1093
-
1094
-    conference.room.addListener(XMPPEvents.ICE_RESTARTING, function () {
1095
-        // All data channels have to be closed, before ICE restart
1096
-        // otherwise Chrome will not trigger "opened" event for the channel
1097
-        // established with the new bridge
1098
-        conference.rtc.closeAllDataChannels();
1099
-    });
1100
-
1101
-    conference.room.addListener(XMPPEvents.LOCAL_UFRAG_CHANGED, function (ufrag) {
1102
-        Statistics.sendLog("Local ufrag: " + ufrag);
1103
-    });
1104
-    conference.room.addListener(XMPPEvents.REMOTE_UFRAG_CHANGED, function (ufrag) {
1105
-        Statistics.sendLog("Remote ufrag: " + ufrag);
1106
-    });
1107
-
1108
-    conference.room.addListener(XMPPEvents.REMOTE_TRACK_ADDED,
1109
-        function (data) {
1110
-            var track = conference.rtc.createRemoteTrack(data);
1111
-            if (track) {
1112
-                conference.onTrackAdded(track);
1113
-            }
1114
-        }
1115
-    );
1116
-    conference.room.addListener(XMPPEvents.REMOTE_TRACK_REMOVED,
1117
-        function (streamId, trackId) {
1118
-            conference.getParticipants().forEach(function(participant) {
1119
-                var tracks = participant.getTracks();
1120
-                for(var i = 0; i < tracks.length; i++) {
1121
-                    if(tracks[i]
1122
-                        && tracks[i].getStreamId() == streamId
1123
-                        && tracks[i].getTrackId() == trackId) {
1124
-                        var track = participant._tracks.splice(i, 1)[0];
1125
-                        conference.eventEmitter.emit(
1126
-                            JitsiConferenceEvents.TRACK_REMOVED, track);
1127
-                        return;
1128
-                    }
1129
-                }
1130
-            });
1131
-        }
1132
-    );
1133
-
1134
-    conference.room.addListener(XMPPEvents.AUDIO_MUTED_BY_FOCUS,
1135
-        function (value) {
1136
-            // set isMutedByFocus when setAudioMute Promise ends
1137
-            conference.rtc.setAudioMute(value).then(
1138
-                function() {
1139
-                    conference.isMutedByFocus = true;
1140
-                },
1141
-                function() {
1142
-                    logger.warn(
1143
-                        "Error while audio muting due to focus request");
1144
-                });
1145
-        }
1146
-    );
1147
-
1148
-    conference.room.addListener(XMPPEvents.SUBJECT_CHANGED, function (subject) {
1149
-        conference.eventEmitter.emit(JitsiConferenceEvents.SUBJECT_CHANGED,
1150
-            subject);
1151
-    });
1152
-
1153
-    conference.room.addListener(XMPPEvents.MUC_JOINED, function () {
1154
-        conference.eventEmitter.emit(JitsiConferenceEvents.CONFERENCE_JOINED);
1155
-    });
1156
-    conference.room.addListener(XMPPEvents.ROOM_JOIN_ERROR, function (pres) {
1157
-        conference.eventEmitter.emit(JitsiConferenceEvents.CONFERENCE_FAILED,
1158
-            JitsiConferenceErrors.CONNECTION_ERROR, pres);
1159
-    });
1160
-    conference.room.addListener(XMPPEvents.ROOM_CONNECT_ERROR, function (pres) {
1161
-        conference.eventEmitter.emit(JitsiConferenceEvents.CONFERENCE_FAILED,
1162
-            JitsiConferenceErrors.CONNECTION_ERROR, pres);
1163
-    });
1164
-    conference.room.addListener(XMPPEvents.ROOM_MAX_USERS_ERROR,
1165
-    function (pres) {
1166
-        conference.eventEmitter.emit(JitsiConferenceEvents.CONFERENCE_FAILED,
1167
-            JitsiConferenceErrors.CONFERENCE_MAX_USERS, pres);
1168
-    });
1169
-    conference.room.addListener(XMPPEvents.PASSWORD_REQUIRED, function (pres) {
1170
-        conference.eventEmitter.emit(JitsiConferenceEvents.CONFERENCE_FAILED, JitsiConferenceErrors.PASSWORD_REQUIRED, pres);
1171
-    });
1172
-    conference.room.addListener(XMPPEvents.AUTHENTICATION_REQUIRED, function () {
1173
-        conference.eventEmitter.emit(JitsiConferenceEvents.CONFERENCE_FAILED, JitsiConferenceErrors.AUTHENTICATION_REQUIRED);
1174
-    });
1175
-    conference.room.addListener(XMPPEvents.BRIDGE_DOWN, function () {
1176
-        conference.eventEmitter.emit(JitsiConferenceEvents.CONFERENCE_FAILED, JitsiConferenceErrors.VIDEOBRIDGE_NOT_AVAILABLE);
1177
-    });
1178
-    conference.room.addListener(XMPPEvents.RESERVATION_ERROR, function (code, msg) {
1179
-        conference.eventEmitter.emit(JitsiConferenceEvents.CONFERENCE_FAILED, JitsiConferenceErrors.RESERVATION_ERROR, code, msg);
1180
-    });
1181
-    conference.room.addListener(XMPPEvents.GRACEFUL_SHUTDOWN, function () {
1182
-        conference.eventEmitter.emit(JitsiConferenceEvents.CONFERENCE_FAILED, JitsiConferenceErrors.GRACEFUL_SHUTDOWN);
1183
-    });
1184
-    conference.room.addListener(XMPPEvents.JINGLE_FATAL_ERROR, function (session, error) {
1185
-        conference.eventEmitter.emit(JitsiConferenceEvents.CONFERENCE_FAILED, JitsiConferenceErrors.JINGLE_FATAL_ERROR, error);
1186
-    });
1187
-    conference.room.addListener(XMPPEvents.MUC_DESTROYED, function (reason) {
1188
-        conference.eventEmitter.emit(JitsiConferenceEvents.CONFERENCE_FAILED, JitsiConferenceErrors.CONFERENCE_DESTROYED, reason);
1189
-    });
1190
-    conference.room.addListener(XMPPEvents.CHAT_ERROR_RECEIVED, function (err, msg) {
1191
-        conference.eventEmitter.emit(JitsiConferenceEvents.CONFERENCE_ERROR, JitsiConferenceErrors.CHAT_ERROR, err, msg);
1192
-    });
1193
-    conference.room.addListener(XMPPEvents.FOCUS_DISCONNECTED, function (focus, retrySec) {
1194
-        conference.eventEmitter.emit(JitsiConferenceEvents.CONFERENCE_FAILED, JitsiConferenceErrors.FOCUS_DISCONNECTED, focus, retrySec);
1195
-    });
1196
-    conference.room.addListener(XMPPEvents.FOCUS_LEFT, function () {
1197
-        conference.eventEmitter.emit(JitsiConferenceEvents.CONFERENCE_FAILED, JitsiConferenceErrors.FOCUS_LEFT);
1198
-    });
1199
-    conference.room.setParticipantPropertyListener(function (node, from) {
1200
-        var participant = conference.getParticipantById(from);
1201
-        if (!participant) {
1202
-            return;
1203
-        }
1204
-
1205
-        participant.setProperty(
1206
-            node.tagName.substring("jitsi_participant_".length),
1207
-            node.value);
1208
-    });
1209
-
1210
-    conference.room.addListener(XMPPEvents.KICKED, function () {
1211
-        conference.eventEmitter.emit(JitsiConferenceEvents.KICKED);
1212
-    });
1213
-
1214
-    conference.room.addListener(XMPPEvents.MUC_MEMBER_JOINED, conference.onMemberJoined.bind(conference));
1215
-    conference.room.addListener(XMPPEvents.MUC_MEMBER_LEFT, conference.onMemberLeft.bind(conference));
1216
-
1217
-    conference.room.addListener(XMPPEvents.DISPLAY_NAME_CHANGED, conference.onDisplayNameChanged.bind(conference));
1218
-
1219
-    conference.room.addListener(XMPPEvents.LOCAL_ROLE_CHANGED, function (role) {
1220
-        conference.eventEmitter.emit(JitsiConferenceEvents.USER_ROLE_CHANGED, conference.myUserId(), role);
1221
-
1222
-        // log all events for the recorder operated by the moderator
1223
-        if (conference.statistics && conference.isModerator()) {
1224
-            conference.on(JitsiConferenceEvents.RECORDER_STATE_CHANGED,
1225
-                function (status, error) {
1226
-                    Statistics.sendLog(
1227
-                        "[Recorder] status: " + status
1228
-                            + (error? " error: " + error : ""));
1229
-                });
1230
-        }
1231
-    });
1232
-    conference.room.addListener(XMPPEvents.MUC_ROLE_CHANGED, conference.onUserRoleChanged.bind(conference));
1233
-
1234
-    conference.room.addListener(XMPPEvents.CONNECTION_INTERRUPTED, function () {
1235
-        conference.eventEmitter.emit(JitsiConferenceEvents.CONNECTION_INTERRUPTED);
1236
-    });
1237
-
1238
-    conference.room.addListener(XMPPEvents.RECORDER_STATE_CHANGED,
1239
-        function (state) {
1240
-            conference.eventEmitter.emit(
1241
-                JitsiConferenceEvents.RECORDER_STATE_CHANGED, state);
1242
-        });
1243
-
1244
-    conference.room.addListener(XMPPEvents.PHONE_NUMBER_CHANGED, function () {
1245
-        conference.eventEmitter.emit(
1246
-            JitsiConferenceEvents.PHONE_NUMBER_CHANGED);
1247
-    });
1248
-
1249
-    conference.room.addListener(XMPPEvents.CONNECTION_RESTORED, function () {
1250
-        conference.eventEmitter.emit(JitsiConferenceEvents.CONNECTION_RESTORED);
1251
-    });
1252
-    conference.room.addListener(XMPPEvents.CONFERENCE_SETUP_FAILED,
1253
-    function (error) {
1254
-        conference.eventEmitter.emit(JitsiConferenceEvents.CONFERENCE_FAILED,
1255
-            JitsiConferenceErrors.SETUP_FAILED, error);
1256
-    });
1257
-
1258
-    conference.room.addListener(AuthenticationEvents.IDENTITY_UPDATED, function (authEnabled, authIdentity) {
1259
-        conference.authEnabled = authEnabled;
1260
-        conference.authIdentity = authIdentity;
1261
-        conference.eventEmitter.emit(JitsiConferenceEvents.AUTH_STATUS_CHANGED, authEnabled, authIdentity);
1262
-    });
1263
-
1264
-    conference.room.addListener(XMPPEvents.MESSAGE_RECEIVED, function (jid, displayName, txt, myJid, ts) {
1265
-        var id = Strophe.getResourceFromJid(jid);
1266
-        conference.eventEmitter.emit(JitsiConferenceEvents.MESSAGE_RECEIVED, id, txt, ts);
1267
-    });
1268
-
1269
-    conference.room.addListener(XMPPEvents.PRESENCE_STATUS, function (jid, status) {
1270
-        var id = Strophe.getResourceFromJid(jid);
1271
-        var participant = conference.getParticipantById(id);
1272
-        if (!participant || participant._status === status) {
1273
-            return;
1274
-        }
1275
-        participant._status = status;
1276
-        conference.eventEmitter.emit(JitsiConferenceEvents.USER_STATUS_CHANGED, id, status);
1277
-    });
1278
-
1279
-    conference.rtc.addListener(RTCEvents.DOMINANTSPEAKER_CHANGED, function (id) {
1280
-        if(conference.lastDominantSpeaker !== id && conference.room) {
1281
-            conference.lastDominantSpeaker = id;
1282
-            conference.eventEmitter.emit(JitsiConferenceEvents.DOMINANT_SPEAKER_CHANGED, id);
1283
-        }
1284
-        if (conference.statistics && conference.myUserId() === id) {
1285
-            // We are the new dominant speaker.
1286
-            conference.statistics.sendDominantSpeakerEvent();
1287
-        }
1288
-    });
1289
-
1290
-    conference.rtc.addListener(RTCEvents.DATA_CHANNEL_OPEN, function () {
1291
-        var now = window.performance.now();
1292
-        logger.log("(TIME) data channel opened ", now);
1293
-        conference.room.connectionTimes["data.channel.opened"] = now;
1294
-    });
1295
-
1296
-    conference.rtc.addListener(RTCEvents.LASTN_CHANGED, function (oldValue, newValue) {
1297
-        conference.eventEmitter.emit(JitsiConferenceEvents.IN_LAST_N_CHANGED, oldValue, newValue);
1298
-    });
1299
-
1300
-    conference.rtc.addListener(RTCEvents.LASTN_ENDPOINT_CHANGED,
1301
-        function (lastNEndpoints, endpointsEnteringLastN) {
1302
-            conference.eventEmitter.emit(JitsiConferenceEvents.LAST_N_ENDPOINTS_CHANGED,
1303
-                lastNEndpoints, endpointsEnteringLastN);
1304
-        });
1305
-
1306
-    conference.xmpp.addListener(XMPPEvents.START_MUTED_FROM_FOCUS,
1307
-        function (audioMuted, videoMuted) {
1308
-            conference.startAudioMuted = audioMuted;
1309
-            conference.startVideoMuted = videoMuted;
1310
-
1311
-            // mute existing local tracks because this is initial mute from
1312
-            // Jicofo
1313
-            conference.getLocalTracks().forEach(function (track) {
1314
-                if (conference.startAudioMuted && track.isAudioTrack()) {
1315
-                    track.mute();
1316
-                }
1317
-                if (conference.startVideoMuted && track.isVideoTrack()) {
1318
-                    track.mute();
1319
-                }
1320
-            });
1321
-
1322
-            conference.eventEmitter.emit(JitsiConferenceEvents.STARTED_MUTED);
1323
-        });
1324
-
1325
-    conference.room.addPresenceListener("startmuted", function (data, from) {
1326
-        var isModerator = false;
1327
-        if (conference.myUserId() === from && conference.isModerator()) {
1328
-            isModerator = true;
1329
-        } else {
1330
-            var participant = conference.getParticipantById(from);
1331
-            if (participant && participant.isModerator()) {
1332
-                isModerator = true;
1333
-            }
1334
-        }
1335
-
1336
-        if (!isModerator) {
1337
-            return;
1338
-        }
1339
-
1340
-        var startAudioMuted = data.attributes.audio === 'true';
1341
-        var startVideoMuted = data.attributes.video === 'true';
1342
-
1343
-        var updated = false;
1344
-
1345
-        if (startAudioMuted !== conference.startMutedPolicy.audio) {
1346
-            conference.startMutedPolicy.audio = startAudioMuted;
1347
-            updated = true;
1348
-        }
1349
-
1350
-        if (startVideoMuted !== conference.startMutedPolicy.video) {
1351
-            conference.startMutedPolicy.video = startVideoMuted;
1352
-            updated = true;
1353
-        }
1354
-
1355
-        if (updated) {
1356
-            conference.eventEmitter.emit(
1357
-                JitsiConferenceEvents.START_MUTED_POLICY_CHANGED,
1358
-                conference.startMutedPolicy
1359
-            );
1360
-        }
1361
-    });
1362
-
1363
-    conference.rtc.addListener(RTCEvents.AVAILABLE_DEVICES_CHANGED, function (devices) {
1364
-        conference.room.updateDeviceAvailability(devices);
1365
-    });
1366
-    conference.room.addPresenceListener("devices", function (data, from) {
1367
-        var isAudioAvailable = false;
1368
-        var isVideoAvailable = false;
1369
-        data.children.forEach(function (config) {
1370
-            if (config.tagName === 'audio') {
1371
-                isAudioAvailable = config.value === 'true';
1372
-            }
1373
-            if (config.tagName === 'video') {
1374
-                isVideoAvailable = config.value === 'true';
1375
-            }
1376
-        });
1377
-
1378
-        var availableDevices;
1379
-        if (conference.myUserId() === from) {
1380
-            availableDevices = conference.availableDevices;
1381
-        } else {
1382
-            var participant = conference.getParticipantById(from);
1383
-            if (!participant) {
1384
-                return;
1385
-            }
1386
-
1387
-            availableDevices = participant._availableDevices;
1388
-        }
1389
-
1390
-        var updated = false;
1391
-
1392
-        if (availableDevices.audio !== isAudioAvailable) {
1393
-            updated = true;
1394
-            availableDevices.audio = isAudioAvailable;
1395
-        }
1396
-
1397
-        if (availableDevices.video !== isVideoAvailable) {
1398
-            updated = true;
1399
-            availableDevices.video = isVideoAvailable;
1400
-        }
1401
-
1402
-        if (updated) {
1403
-            conference.eventEmitter.emit(
1404
-                JitsiConferenceEvents.AVAILABLE_DEVICES_CHANGED,
1405
-                from, availableDevices);
1406
-        }
1407
-    });
1408
-
1409
-    if(conference.statistics) {
1410
-        //FIXME: Maybe remove event should not be associated with the conference.
1411
-        conference.statistics.addAudioLevelListener(function (ssrc, level) {
1412
-            var userId = null;
1413
-
1414
-            var resource = conference.rtc.getResourceBySSRC(ssrc);
1415
-            if (!resource)
1416
-                return;
1417
-
1418
-            conference.rtc.setAudioLevel(resource, level);
1419
-        });
1420
-
1421
-        conference.statistics.addAudioProblemListener(function (ssrc) {
1422
-            conference._reportAudioProblem(ssrc)
1423
-        });
1424
-
1425
-        conference.statistics.addConnectionStatsListener(function (stats) {
1426
-            var ssrc2resolution = stats.resolution;
1427
-
1428
-            var id2resolution = {};
1429
-
1430
-            // preprocess resolutions: group by user id, skip incorrect
1431
-            // resolutions etc.
1432
-            Object.keys(ssrc2resolution).forEach(function (ssrc) {
1433
-                var resolution = ssrc2resolution[ssrc];
1434
-
1435
-                if (!resolution.width || !resolution.height ||
1436
-                    resolution.width == -1 || resolution.height == -1) {
1437
-                    return;
1438
-                }
1439
-
1440
-                var id = conference.rtc.getResourceBySSRC(ssrc);
1441
-                if (!id) {
1442
-                    return;
1443
-                }
1444
-
1445
-                // ssrc to resolution map for user id
1446
-                var idResolutions = id2resolution[id] || {};
1447
-                idResolutions[ssrc] = resolution;
1448
-
1449
-                id2resolution[id] = idResolutions;
1450
-            });
1451
-
1452
-            stats.resolution = id2resolution;
1453
-
1454
-            conference.eventEmitter.emit(
1455
-                JitsiConferenceEvents.CONNECTION_STATS, stats);
1456
-        });
1457
-        conference.room.addListener(XMPPEvents.DISPOSE_CONFERENCE,
1458
-            function () {
1459
-                conference.statistics.dispose();
1460
-            });
1461
-
1462
-        conference.room.addListener(XMPPEvents.CONNECTION_ICE_FAILED,
1463
-            function (pc) {
1464
-                conference.statistics.sendIceConnectionFailedEvent(pc);
1465
-                conference.room.eventEmitter.emit(
1466
-                    XMPPEvents.CONFERENCE_SETUP_FAILED,
1467
-                    new Error("ICE fail"));
1468
-            });
1469
-
1470
-        conference.rtc.addListener(RTCEvents.TRACK_ATTACHED,
1471
-            function(track, container) {
1472
-                var ssrc = track.getSSRC();
1473
-                if (!container.id || !ssrc) {
1474
-                    return;
1475
-                }
1476
-                conference.statistics.associateStreamWithVideoTag(
1477
-                    ssrc, track.isLocal(), track.getUsageLabel(), container.id);
1478
-
1479
-            });
1480
-
1481
-        conference.on(JitsiConferenceEvents.TRACK_MUTE_CHANGED,
1482
-            function (track) {
1483
-                if(!track.isLocal())
1484
-                    return;
1485
-                var type = (track.getType() === "audio")? "audio" : "video";
1486
-                conference.statistics.sendMuteEvent(track.isMuted(), type);
1487
-            });
1488
-
1489
-        conference.room.addListener(XMPPEvents.CREATE_OFFER_FAILED, function (e, pc) {
1490
-            conference.statistics.sendCreateOfferFailed(e, pc);
1491
-        });
1492
-
1493
-        conference.room.addListener(XMPPEvents.CREATE_ANSWER_FAILED, function (e, pc) {
1494
-            conference.statistics.sendCreateAnswerFailed(e, pc);
1495
-        });
1496
-
1497
-        conference.room.addListener(XMPPEvents.SET_LOCAL_DESCRIPTION_FAILED,
1498
-            function (e, pc) {
1499
-                conference.statistics.sendSetLocalDescFailed(e, pc);
1500
-            }
1501
-        );
1502
-
1503
-        conference.room.addListener(XMPPEvents.SET_REMOTE_DESCRIPTION_FAILED,
1504
-            function (e, pc) {
1505
-                conference.statistics.sendSetRemoteDescFailed(e, pc);
1506
-            }
1507
-        );
1508
-
1509
-        conference.room.addListener(XMPPEvents.ADD_ICE_CANDIDATE_FAILED,
1510
-            function (e, pc) {
1511
-                conference.statistics.sendAddIceCandidateFailed(e, pc);
1512
-            }
1513
-        );
1514
-    }
1515
-}
1215
+JitsiConference.prototype._isFocus = function (mucJid) {
1216
+    return this.room.isFocus(mucJid);
1217
+};
1516 1218
 
1219
+/**
1220
+ * Fires CONFERENCE_FAILED event with INCOMPATIBLE_SERVER_VERSIONS parameter
1221
+ */
1222
+JitsiConference.prototype._fireIncompatibleVersionsEvent = function () {
1223
+    this.eventEmitter.emit(JitsiConferenceEvents.CONFERENCE_FAILED,
1224
+        JitsiConferenceErrors.INCOMPATIBLE_SERVER_VERSIONS);
1225
+};
1517 1226
 
1518 1227
 module.exports = JitsiConference;

+ 6
- 1
JitsiConferenceErrors.js Просмотреть файл

@@ -59,7 +59,12 @@ var JitsiConferenceErrors = {
59 59
     /**
60 60
      * Indicates that max users limit has been reached.
61 61
      */
62
-    CONFERENCE_MAX_USERS: "conference.max_users"
62
+    CONFERENCE_MAX_USERS: "conference.max_users",
63
+    /**
64
+     * Indicates that the versions of the server side components are
65
+     * incompatible with the client side.
66
+     */
67
+    INCOMPATIBLE_SERVER_VERSIONS: "conference.incompatible_server_versions"
63 68
     /**
64 69
      * Many more errors TBD here.
65 70
      */

+ 501
- 0
JitsiConferenceEventManager.js Просмотреть файл

@@ -0,0 +1,501 @@
1
+/* global Strophe */
2
+var logger = require("jitsi-meet-logger").getLogger(__filename);
3
+var EventEmitterForwarder = require("./modules/util/EventEmitterForwarder");
4
+var XMPPEvents = require("./service/xmpp/XMPPEvents");
5
+var RTCEvents = require("./service/RTC/RTCEvents");
6
+var JitsiConferenceEvents = require("./JitsiConferenceEvents");
7
+var JitsiConferenceErrors = require("./JitsiConferenceErrors");
8
+var AuthenticationEvents =
9
+    require("./service/authentication/AuthenticationEvents");
10
+var Statistics = require("./modules/statistics/statistics");
11
+var MediaType = require("./service/RTC/MediaType");
12
+
13
+/**
14
+ * Setups all event listeners related to conference
15
+ * @param conference {JitsiConference} the conference
16
+ */
17
+function JitsiConferenceEventManager(conference) {
18
+    this.conference = conference;
19
+
20
+    //Listeners related to the conference only
21
+    conference.on(JitsiConferenceEvents.TRACK_MUTE_CHANGED,
22
+        function (track) {
23
+            if(!track.isLocal() || !conference.statistics)
24
+                return;
25
+            conference.statistics.sendMuteEvent(track.isMuted(),
26
+                track.getType());
27
+        });
28
+}
29
+
30
+/**
31
+ * Setups event listeners related to conference.chatRoom
32
+ */
33
+JitsiConferenceEventManager.prototype.setupChatRoomListeners = function () {
34
+    var conference = this.conference;
35
+    var chatRoom = conference.room;
36
+    this.chatRoomForwarder = new EventEmitterForwarder(chatRoom,
37
+        this.conference.eventEmitter);
38
+
39
+    chatRoom.addListener(XMPPEvents.ICE_RESTARTING, function () {
40
+        // All data channels have to be closed, before ICE restart
41
+        // otherwise Chrome will not trigger "opened" event for the channel
42
+        // established with the new bridge
43
+        conference.rtc.closeAllDataChannels();
44
+    });
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
+                        conference.eventEmitter.emit(
64
+                            JitsiConferenceEvents.TRACK_REMOVED, track);
65
+                        return;
66
+                    }
67
+                }
68
+            });
69
+        }
70
+    );
71
+
72
+    chatRoom.addListener(XMPPEvents.AUDIO_MUTED_BY_FOCUS,
73
+        function (value) {
74
+            // set isMutedByFocus when setAudioMute Promise ends
75
+            conference.rtc.setAudioMute(value).then(
76
+                function() {
77
+                    conference.isMutedByFocus = true;
78
+                },
79
+                function() {
80
+                    logger.warn(
81
+                        "Error while audio muting due to focus request");
82
+                });
83
+        }
84
+    );
85
+
86
+    this.chatRoomForwarder.forward(XMPPEvents.SUBJECT_CHANGED,
87
+        JitsiConferenceEvents.SUBJECT_CHANGED);
88
+
89
+    this.chatRoomForwarder.forward(XMPPEvents.MUC_JOINED,
90
+        JitsiConferenceEvents.CONFERENCE_JOINED);
91
+
92
+    this.chatRoomForwarder.forward(XMPPEvents.ROOM_JOIN_ERROR,
93
+        JitsiConferenceEvents.CONFERENCE_FAILED,
94
+        JitsiConferenceErrors.CONNECTION_ERROR);
95
+
96
+    this.chatRoomForwarder.forward(XMPPEvents.ROOM_CONNECT_ERROR,
97
+        JitsiConferenceEvents.CONFERENCE_FAILED,
98
+        JitsiConferenceErrors.CONNECTION_ERROR);
99
+
100
+    this.chatRoomForwarder.forward(XMPPEvents.ROOM_MAX_USERS_ERROR,
101
+        JitsiConferenceEvents.CONFERENCE_FAILED,
102
+        JitsiConferenceErrors.CONFERENCE_MAX_USERS);
103
+
104
+    this.chatRoomForwarder.forward(XMPPEvents.PASSWORD_REQUIRED,
105
+        JitsiConferenceEvents.CONFERENCE_FAILED,
106
+        JitsiConferenceErrors.PASSWORD_REQUIRED);
107
+
108
+    this.chatRoomForwarder.forward(XMPPEvents.AUTHENTICATION_REQUIRED,
109
+        JitsiConferenceEvents.CONFERENCE_FAILED,
110
+        JitsiConferenceErrors.AUTHENTICATION_REQUIRED);
111
+
112
+    this.chatRoomForwarder.forward(XMPPEvents.BRIDGE_DOWN,
113
+        JitsiConferenceEvents.CONFERENCE_FAILED,
114
+        JitsiConferenceErrors.VIDEOBRIDGE_NOT_AVAILABLE);
115
+
116
+    this.chatRoomForwarder.forward(XMPPEvents.RESERVATION_ERROR,
117
+        JitsiConferenceEvents.CONFERENCE_FAILED,
118
+        JitsiConferenceErrors.RESERVATION_ERROR);
119
+
120
+    this.chatRoomForwarder.forward(XMPPEvents.GRACEFUL_SHUTDOWN,
121
+        JitsiConferenceEvents.CONFERENCE_FAILED,
122
+        JitsiConferenceErrors.GRACEFUL_SHUTDOWN);
123
+
124
+    chatRoom.addListener(XMPPEvents.JINGLE_FATAL_ERROR,
125
+        function (session, error) {
126
+            conference.eventEmitter.emit(
127
+                JitsiConferenceEvents.CONFERENCE_FAILED,
128
+                JitsiConferenceErrors.JINGLE_FATAL_ERROR, error);
129
+        });
130
+
131
+    chatRoom.addListener(XMPPEvents.CONNECTION_ICE_FAILED,
132
+        function (pc) {
133
+            chatRoom.eventEmitter.emit(
134
+                XMPPEvents.CONFERENCE_SETUP_FAILED,
135
+                new Error("ICE fail"));
136
+        });
137
+
138
+    this.chatRoomForwarder.forward(XMPPEvents.MUC_DESTROYED,
139
+        JitsiConferenceEvents.CONFERENCE_FAILED,
140
+        JitsiConferenceErrors.CONFERENCE_DESTROYED);
141
+
142
+    this.chatRoomForwarder.forward(XMPPEvents.CHAT_ERROR_RECEIVED,
143
+        JitsiConferenceEvents.CONFERENCE_ERROR,
144
+        JitsiConferenceErrors.CHAT_ERROR);
145
+
146
+    this.chatRoomForwarder.forward(XMPPEvents.FOCUS_DISCONNECTED,
147
+        JitsiConferenceEvents.CONFERENCE_FAILED,
148
+        JitsiConferenceErrors.FOCUS_DISCONNECTED);
149
+
150
+    chatRoom.addListener(XMPPEvents.FOCUS_LEFT,
151
+        function () {
152
+            if(!conference.connection._reload())
153
+                conference.eventEmitter.emit(
154
+                    JitsiConferenceEvents.CONFERENCE_FAILED,
155
+                    JitsiConferenceErrors.FOCUS_LEFT);
156
+        });
157
+
158
+    chatRoom.addListener(XMPPEvents.ALLOCATE_FOCUS_MAX_RETRIES_ERROR,
159
+        function () {
160
+            conference.connection._reload();
161
+        });
162
+
163
+    this.chatRoomForwarder.forward(XMPPEvents.CONNECTION_INTERRUPTED,
164
+        JitsiConferenceEvents.CONNECTION_INTERRUPTED);
165
+
166
+    this.chatRoomForwarder.forward(XMPPEvents.RECORDER_STATE_CHANGED,
167
+        JitsiConferenceEvents.RECORDER_STATE_CHANGED);
168
+
169
+    this.chatRoomForwarder.forward(XMPPEvents.PHONE_NUMBER_CHANGED,
170
+        JitsiConferenceEvents.PHONE_NUMBER_CHANGED);
171
+
172
+    this.chatRoomForwarder.forward(XMPPEvents.CONNECTION_RESTORED,
173
+        JitsiConferenceEvents.CONNECTION_RESTORED);
174
+
175
+    this.chatRoomForwarder.forward(XMPPEvents.CONFERENCE_SETUP_FAILED,
176
+        JitsiConferenceEvents.CONFERENCE_FAILED,
177
+        JitsiConferenceErrors.SETUP_FAILED);
178
+
179
+    chatRoom.setParticipantPropertyListener(function (node, from) {
180
+        var participant = conference.getParticipantById(from);
181
+        if (!participant) {
182
+            return;
183
+        }
184
+
185
+        participant.setProperty(
186
+            node.tagName.substring("jitsi_participant_".length),
187
+            node.value);
188
+    });
189
+
190
+    this.chatRoomForwarder.forward(XMPPEvents.KICKED,
191
+        JitsiConferenceEvents.KICKED);
192
+
193
+    chatRoom.addListener(XMPPEvents.MUC_MEMBER_JOINED,
194
+        conference.onMemberJoined.bind(conference));
195
+    chatRoom.addListener(XMPPEvents.MUC_MEMBER_LEFT,
196
+        conference.onMemberLeft.bind(conference));
197
+
198
+    chatRoom.addListener(XMPPEvents.DISPLAY_NAME_CHANGED,
199
+        conference.onDisplayNameChanged.bind(conference));
200
+
201
+    chatRoom.addListener(XMPPEvents.LOCAL_ROLE_CHANGED, function (role) {
202
+        conference.eventEmitter.emit(JitsiConferenceEvents.USER_ROLE_CHANGED,
203
+            conference.myUserId(), role);
204
+
205
+        // log all events for the recorder operated by the moderator
206
+        if (conference.statistics && conference.isModerator()) {
207
+            conference.on(JitsiConferenceEvents.RECORDER_STATE_CHANGED,
208
+                function (status, error) {
209
+                    Statistics.sendLog("[Recorder] status: " + status
210
+                        + (error? " error: " + error : ""));
211
+                });
212
+        }
213
+    });
214
+
215
+    chatRoom.addListener(XMPPEvents.MUC_ROLE_CHANGED,
216
+        conference.onUserRoleChanged.bind(conference));
217
+
218
+    chatRoom.addListener(AuthenticationEvents.IDENTITY_UPDATED,
219
+        function (authEnabled, authIdentity) {
220
+            conference.authEnabled = authEnabled;
221
+            conference.authIdentity = authIdentity;
222
+            conference.eventEmitter.emit(
223
+                JitsiConferenceEvents.AUTH_STATUS_CHANGED, authEnabled,
224
+                authIdentity);
225
+        });
226
+
227
+    chatRoom.addListener(XMPPEvents.MESSAGE_RECEIVED,
228
+        function (jid, displayName, txt, myJid, ts) {
229
+            var id = Strophe.getResourceFromJid(jid);
230
+            conference.eventEmitter.emit(JitsiConferenceEvents.MESSAGE_RECEIVED,
231
+                id, txt, ts);
232
+        });
233
+
234
+    chatRoom.addListener(XMPPEvents.PRESENCE_STATUS,
235
+        function (jid, status) {
236
+            var id = Strophe.getResourceFromJid(jid);
237
+            var participant = conference.getParticipantById(id);
238
+            if (!participant || participant._status === status) {
239
+                return;
240
+            }
241
+            participant._status = status;
242
+            conference.eventEmitter.emit(
243
+                JitsiConferenceEvents.USER_STATUS_CHANGED, id, status);
244
+        });
245
+
246
+    chatRoom.addPresenceListener("startmuted", function (data, from) {
247
+        var isModerator = false;
248
+        if (conference.myUserId() === from && conference.isModerator()) {
249
+            isModerator = true;
250
+        } else {
251
+            var participant = conference.getParticipantById(from);
252
+            if (participant && participant.isModerator()) {
253
+                isModerator = true;
254
+            }
255
+        }
256
+
257
+        if (!isModerator) {
258
+            return;
259
+        }
260
+
261
+        var startAudioMuted = data.attributes.audio === 'true';
262
+        var startVideoMuted = data.attributes.video === 'true';
263
+
264
+        var updated = false;
265
+
266
+        if (startAudioMuted !== conference.startMutedPolicy.audio) {
267
+            conference.startMutedPolicy.audio = startAudioMuted;
268
+            updated = true;
269
+        }
270
+
271
+        if (startVideoMuted !== conference.startMutedPolicy.video) {
272
+            conference.startMutedPolicy.video = startVideoMuted;
273
+            updated = true;
274
+        }
275
+
276
+        if (updated) {
277
+            conference.eventEmitter.emit(
278
+                JitsiConferenceEvents.START_MUTED_POLICY_CHANGED,
279
+                conference.startMutedPolicy
280
+            );
281
+        }
282
+    });
283
+
284
+    chatRoom.addPresenceListener("videomuted", function (values, from) {
285
+        conference.rtc.handleRemoteTrackMute(MediaType.VIDEO,
286
+            values.value == "true", from);
287
+    });
288
+
289
+    chatRoom.addPresenceListener("audiomuted", function (values, from) {
290
+        conference.rtc.handleRemoteTrackMute(MediaType.AUDIO,
291
+            values.value == "true", from);
292
+    });
293
+
294
+    chatRoom.addPresenceListener("videoType", function(data, from) {
295
+        conference.rtc.handleRemoteTrackVideoTypeChanged(data.value, from);
296
+    });
297
+
298
+    chatRoom.addPresenceListener("devices", function (data, from) {
299
+        var isAudioAvailable = false;
300
+        var isVideoAvailable = false;
301
+        data.children.forEach(function (config) {
302
+            if (config.tagName === 'audio') {
303
+                isAudioAvailable = config.value === 'true';
304
+            }
305
+            if (config.tagName === 'video') {
306
+                isVideoAvailable = config.value === 'true';
307
+            }
308
+        });
309
+
310
+        var availableDevices;
311
+        if (conference.myUserId() === from) {
312
+            availableDevices = conference.availableDevices;
313
+        } else {
314
+            var participant = conference.getParticipantById(from);
315
+            if (!participant) {
316
+                return;
317
+            }
318
+
319
+            availableDevices = participant._availableDevices;
320
+        }
321
+
322
+        var updated = false;
323
+
324
+        if (availableDevices.audio !== isAudioAvailable) {
325
+            updated = true;
326
+            availableDevices.audio = isAudioAvailable;
327
+        }
328
+
329
+        if (availableDevices.video !== isVideoAvailable) {
330
+            updated = true;
331
+            availableDevices.video = isVideoAvailable;
332
+        }
333
+
334
+        if (updated) {
335
+            conference.eventEmitter.emit(
336
+                JitsiConferenceEvents.AVAILABLE_DEVICES_CHANGED,
337
+                from, availableDevices);
338
+        }
339
+    });
340
+
341
+    if(conference.statistics) {
342
+        chatRoom.addListener(XMPPEvents.DISPOSE_CONFERENCE,
343
+            function () {
344
+                conference.statistics.dispose();
345
+            });
346
+
347
+        chatRoom.addListener(XMPPEvents.CONNECTION_ICE_FAILED,
348
+            function (pc) {
349
+                conference.statistics.sendIceConnectionFailedEvent(pc);
350
+            });
351
+
352
+        chatRoom.addListener(XMPPEvents.CREATE_OFFER_FAILED,
353
+            function (e, pc) {
354
+                conference.statistics.sendCreateOfferFailed(e, pc);
355
+            });
356
+
357
+        chatRoom.addListener(XMPPEvents.CREATE_ANSWER_FAILED,
358
+            function (e, pc) {
359
+                conference.statistics.sendCreateAnswerFailed(e, pc);
360
+            });
361
+
362
+        chatRoom.addListener(XMPPEvents.SET_LOCAL_DESCRIPTION_FAILED,
363
+            function (e, pc) {
364
+                conference.statistics.sendSetLocalDescFailed(e, pc);
365
+            });
366
+
367
+        chatRoom.addListener(XMPPEvents.SET_REMOTE_DESCRIPTION_FAILED,
368
+            function (e, pc) {
369
+                conference.statistics.sendSetRemoteDescFailed(e, pc);
370
+            });
371
+
372
+        chatRoom.addListener(XMPPEvents.ADD_ICE_CANDIDATE_FAILED,
373
+            function (e, pc) {
374
+                conference.statistics.sendAddIceCandidateFailed(e, pc);
375
+            });
376
+    }
377
+};
378
+
379
+/**
380
+ * Setups event listeners related to conference.rtc
381
+ */
382
+JitsiConferenceEventManager.prototype.setupRTCListeners = function () {
383
+    var conference = this.conference;
384
+    conference.rtc.addListener(RTCEvents.DOMINANTSPEAKER_CHANGED,
385
+        function (id) {
386
+            if(conference.lastDominantSpeaker !== id && conference.room) {
387
+                conference.lastDominantSpeaker = id;
388
+                conference.eventEmitter.emit(
389
+                    JitsiConferenceEvents.DOMINANT_SPEAKER_CHANGED, id);
390
+            }
391
+            if (conference.statistics && conference.myUserId() === id) {
392
+                // We are the new dominant speaker.
393
+                conference.statistics.sendDominantSpeakerEvent();
394
+            }
395
+        });
396
+
397
+    conference.rtc.addListener(RTCEvents.DATA_CHANNEL_OPEN, function () {
398
+        var now = window.performance.now();
399
+        logger.log("(TIME) data channel opened ", now);
400
+        conference.room.connectionTimes["data.channel.opened"] = now;
401
+    });
402
+
403
+    conference.rtc.addListener(RTCEvents.LASTN_CHANGED,
404
+        function (oldValue, newValue) {
405
+            conference.eventEmitter.emit(
406
+                JitsiConferenceEvents.IN_LAST_N_CHANGED, oldValue, newValue);
407
+        });
408
+
409
+    conference.rtc.addListener(RTCEvents.LASTN_ENDPOINT_CHANGED,
410
+        function (lastNEndpoints, endpointsEnteringLastN) {
411
+            conference.eventEmitter.emit(
412
+                JitsiConferenceEvents.LAST_N_ENDPOINTS_CHANGED,
413
+                lastNEndpoints, endpointsEnteringLastN);
414
+        });
415
+
416
+    conference.rtc.addListener(RTCEvents.AVAILABLE_DEVICES_CHANGED,
417
+        function (devices) {
418
+            conference.room.updateDeviceAvailability(devices);
419
+        });
420
+};
421
+
422
+/**
423
+ * Setups event listeners related to conference.xmpp
424
+ */
425
+JitsiConferenceEventManager.prototype.setupXMPPListeners = function () {
426
+    var conference = this.conference;
427
+    conference.xmpp.addListener(
428
+        XMPPEvents.CALL_INCOMING, conference.onIncomingCall.bind(conference));
429
+
430
+    conference.xmpp.addListener(XMPPEvents.START_MUTED_FROM_FOCUS,
431
+        function (audioMuted, videoMuted) {
432
+            conference.startAudioMuted = audioMuted;
433
+            conference.startVideoMuted = videoMuted;
434
+
435
+            // mute existing local tracks because this is initial mute from
436
+            // Jicofo
437
+            conference.getLocalTracks().forEach(function (track) {
438
+                switch (track.getType()) {
439
+                    case MediaType.AUDIO:
440
+                        conference.startAudioMuted && track.mute();
441
+                        break;
442
+                    case MediaType.VIDEO:
443
+                        conference.startVideoMuted && track.mute();
444
+                        break;
445
+                }
446
+            });
447
+
448
+            conference.eventEmitter.emit(JitsiConferenceEvents.STARTED_MUTED);
449
+        });
450
+};
451
+
452
+/**
453
+ * Setups event listeners related to conference.statistics
454
+ */
455
+JitsiConferenceEventManager.prototype.setupStatisticsListeners = function () {
456
+    var conference = this.conference;
457
+    if(!conference.statistics)
458
+        return;
459
+
460
+    conference.statistics.addAudioLevelListener(function (ssrc, level) {
461
+        var resource = conference.rtc.getResourceBySSRC(ssrc);
462
+        if (!resource)
463
+            return;
464
+
465
+        conference.rtc.setAudioLevel(resource, level);
466
+    });
467
+    conference.statistics.addConnectionStatsListener(function (stats) {
468
+        var ssrc2resolution = stats.resolution;
469
+
470
+        var id2resolution = {};
471
+
472
+        // preprocess resolutions: group by user id, skip incorrect
473
+        // resolutions etc.
474
+        Object.keys(ssrc2resolution).forEach(function (ssrc) {
475
+            var resolution = ssrc2resolution[ssrc];
476
+
477
+            if (!resolution.width || !resolution.height ||
478
+                resolution.width == -1 || resolution.height == -1) {
479
+                return;
480
+            }
481
+
482
+            var id = conference.rtc.getResourceBySSRC(ssrc);
483
+            if (!id) {
484
+                return;
485
+            }
486
+
487
+            // ssrc to resolution map for user id
488
+            var idResolutions = id2resolution[id] || {};
489
+            idResolutions[ssrc] = resolution;
490
+
491
+            id2resolution[id] = idResolutions;
492
+        });
493
+
494
+        stats.resolution = id2resolution;
495
+
496
+        conference.eventEmitter.emit(
497
+            JitsiConferenceEvents.CONNECTION_STATS, stats);
498
+    });
499
+};
500
+
501
+module.exports = JitsiConferenceEventManager;

+ 51
- 0
JitsiConnection.js Просмотреть файл

@@ -1,5 +1,7 @@
1 1
 var JitsiConference = require("./JitsiConference");
2 2
 var XMPP = require("./modules/xmpp/xmpp");
3
+var JitsiConnectionEvents = require("./JitsiConnectionEvents");
4
+var JitsiConnectionErrors = require("./JitsiConnectionErrors");
3 5
 
4 6
 /**
5 7
  * Creates new connection object for the Jitsi Meet server side video conferencing service. Provides access to the
@@ -15,6 +17,20 @@ function JitsiConnection(appID, token, options) {
15 17
     this.options = options;
16 18
     this.xmpp = new XMPP(options, token);
17 19
     this.conferences = {};
20
+    this.retryOnFail = 0;
21
+    this.addEventListener(JitsiConnectionEvents.CONNECTION_ESTABLISHED,
22
+        function () {
23
+            this.retryOnFail = 3;
24
+        }.bind(this));
25
+
26
+    this.addEventListener(JitsiConnectionEvents.CONNECTION_FAILED,
27
+        function (errType, msg) {
28
+            if(errType === JitsiConnectionErrors.OTHER_ERROR &&
29
+                (msg === "item-not-found" || msg === "host-unknown")) {
30
+                    // FIXME: don't report the error if we are going to reload
31
+                    this._reload();
32
+                }
33
+        }.bind(this));
18 34
 }
19 35
 
20 36
 /**
@@ -40,6 +56,41 @@ JitsiConnection.prototype.attach = function (options) {
40 56
     this.xmpp.attach(options);
41 57
 }
42 58
 
59
+/**
60
+ * Reloads the JitsiConnection instance and all related conferences
61
+ */
62
+JitsiConnection.prototype._reload = function () {
63
+    if(this.retryOnFail === 0)
64
+        return false;
65
+    this.retryOnFail--;
66
+    var states = {};
67
+    for(var name in this.conferences) {
68
+        states[name] = this.conferences[name].room.exportState();
69
+        this.conferences[name].leave(true);
70
+    }
71
+    this.connectionEstablishedHandler =
72
+        this._reloadConferences.bind(this, states);
73
+    this.addEventListener(JitsiConnectionEvents.CONNECTION_ESTABLISHED,
74
+        this.connectionEstablishedHandler);
75
+    this.xmpp.reload();
76
+    return true;
77
+}
78
+
79
+/**
80
+ * Reloads all conferences related to this JitsiConnection instance
81
+ * @param states {object} the exported states per conference
82
+ */
83
+JitsiConnection.prototype._reloadConferences = function (states) {
84
+    this.removeEventListener(JitsiConnectionEvents.CONNECTION_ESTABLISHED,
85
+        this.connectionEstablishedHandler);
86
+    this.connectionEstablishedHandler = null;
87
+    states = states || {};
88
+    for(var name in this.conferences) {
89
+        this.conferences[name]._init({roomState: states[name]});
90
+        this.conferences[name].join();
91
+    }
92
+}
93
+
43 94
 /**
44 95
  * Disconnect the client from the server.
45 96
  */

+ 20
- 28
modules/RTC/JitsiLocalTrack.js Просмотреть файл

@@ -38,8 +38,6 @@ function JitsiLocalTrack(stream, track, mediaType, videoType, resolution,
38 38
     this.deviceId = deviceId;
39 39
     this.startMuted = false;
40 40
     this.disposed = false;
41
-    //FIXME: This dependacy is not necessary.
42
-    this.conference = null;
43 41
     this.initialMSID = this.getMSID();
44 42
     this.inMuteOrUnmuteProgress = false;
45 43
 
@@ -170,7 +168,7 @@ JitsiLocalTrack.prototype._setMute = function (mute, resolve, reject) {
170 168
         resolve();
171 169
         return;
172 170
     }
173
-    if(!this.rtc) {
171
+    if(!this.conference) {
174 172
         this.startMuted = mute;
175 173
         resolve();
176 174
         return;
@@ -197,19 +195,21 @@ JitsiLocalTrack.prototype._setMute = function (mute, resolve, reject) {
197 195
         if (this.track)
198 196
             this.track.enabled = !mute;
199 197
         if(isAudio)
200
-            this.rtc.room.setAudioMute(mute, callbackFunction);
198
+            this.conference.room.setAudioMute(mute, callbackFunction);
201 199
         else
202
-            this.rtc.room.setVideoMute(mute, callbackFunction);
200
+            this.conference.room.setVideoMute(mute, callbackFunction);
203 201
     } else {
204 202
         if (mute) {
205 203
             this.dontFireRemoveEvent = true;
206
-            this.rtc.room.removeStream(this.stream, function () {
204
+            this.conference.room.removeStream(this.stream, function () {
207 205
                     RTCUtils.stopMediaStream(this.stream);
208 206
                     setStreamToNull = true;
209 207
                     if(isAudio)
210
-                        this.rtc.room.setAudioMute(mute, callbackFunction);
208
+                        this.conference.room.setAudioMute(mute,
209
+                            callbackFunction);
211 210
                     else
212
-                        this.rtc.room.setVideoMute(mute, callbackFunction);
211
+                        this.conference.room.setVideoMute(mute,
212
+                            callbackFunction);
213 213
                     //FIXME: Maybe here we should set the SRC for the containers to something
214 214
                 }.bind(this),
215 215
                 function (error) {
@@ -262,13 +262,13 @@ JitsiLocalTrack.prototype._setMute = function (mute, resolve, reject) {
262 262
                                     self.containers[i], self.stream);
263 263
                     }
264 264
 
265
-                    self.rtc.room.addStream(self.stream,
265
+                    self.conference.room.addStream(self.stream,
266 266
                         function () {
267 267
                             if(isAudio)
268
-                                self.rtc.room.setAudioMute(
268
+                                self.conference.room.setAudioMute(
269 269
                                     mute, callbackFunction);
270 270
                             else
271
-                                self.rtc.room.setVideoMute(
271
+                                self.conference.room.setVideoMute(
272 272
                                     mute, callbackFunction);
273 273
                         }, function (error) {
274 274
                             reject(error);
@@ -331,22 +331,6 @@ JitsiLocalTrack.prototype.isMuted = function () {
331 331
     }
332 332
 };
333 333
 
334
-/**
335
- * Private method. Updates rtc property of the track.
336
- * @param rtc the rtc instance.
337
- */
338
-JitsiLocalTrack.prototype._setRTC = function (rtc) {
339
-    this.rtc = rtc;
340
-    // We want to keep up with postponed events which should have been fired
341
-    // on "attach" call, but for local track we not always have the conference
342
-    // before attaching. However this may result in duplicated events if they
343
-    // have been triggered on "attach" already.
344
-    for(var i = 0; i < this.containers.length; i++)
345
-    {
346
-        this._maybeFireTrackAttached(this.containers[i]);
347
-    }
348
-};
349
-
350 334
 /**
351 335
  * Updates the SSRC associated with the MediaStream in JitsiLocalTrack object.
352 336
  * @ssrc the new ssrc
@@ -356,7 +340,6 @@ JitsiLocalTrack.prototype._setSSRC = function (ssrc) {
356 340
 };
357 341
 
358 342
 
359
-//FIXME: This dependacy is not necessary. This is quick fix.
360 343
 /**
361 344
  * Sets the JitsiConference object associated with the track. This is temp
362 345
  * solution.
@@ -364,6 +347,15 @@ JitsiLocalTrack.prototype._setSSRC = function (ssrc) {
364 347
  */
365 348
 JitsiLocalTrack.prototype._setConference = function(conference) {
366 349
     this.conference = conference;
350
+
351
+    // We want to keep up with postponed events which should have been fired
352
+    // on "attach" call, but for local track we not always have the conference
353
+    // before attaching. However this may result in duplicated events if they
354
+    // have been triggered on "attach" already.
355
+    for(var i = 0; i < this.containers.length; i++)
356
+    {
357
+        this._maybeFireTrackAttached(this.containers[i]);
358
+    }
367 359
 };
368 360
 
369 361
 /**

+ 3
- 3
modules/RTC/JitsiRemoteTrack.js Просмотреть файл

@@ -13,11 +13,11 @@ var JitsiTrackEvents = require("../../JitsiTrackEvents");
13 13
  * @param muted intial muted state of the JitsiRemoteTrack
14 14
  * @constructor
15 15
  */
16
-function JitsiRemoteTrack(RTC, ownerJid, stream, track, mediaType, videoType,
16
+function JitsiRemoteTrack(conference, ownerJid, stream, track, mediaType, videoType,
17 17
                           ssrc, muted) {
18 18
     JitsiTrack.call(
19
-        this, RTC, stream, track, function () {}, mediaType, videoType, ssrc);
20
-    this.rtc = RTC;
19
+        this, conference, stream, track, function () {}, mediaType, videoType, ssrc);
20
+    this.conference = conference;
21 21
     this.peerjid = ownerJid;
22 22
     this.muted = muted;
23 23
 }

+ 4
- 4
modules/RTC/JitsiTrack.js Просмотреть файл

@@ -54,7 +54,7 @@ function addMediaStreamInactiveHandler(mediaStream, handler) {
54 54
  * @param videoType the VideoType for this track if any
55 55
  * @param ssrc the SSRC of this track if known
56 56
  */
57
-function JitsiTrack(rtc, stream, track, streamInactiveHandler, trackMediaType,
57
+function JitsiTrack(conference, stream, track, streamInactiveHandler, trackMediaType,
58 58
                     videoType, ssrc)
59 59
 {
60 60
     /**
@@ -62,7 +62,7 @@ function JitsiTrack(rtc, stream, track, streamInactiveHandler, trackMediaType,
62 62
      * @type {Array}
63 63
      */
64 64
     this.containers = [];
65
-    this.rtc = rtc;
65
+    this.conference = conference;
66 66
     this.stream = stream;
67 67
     this.ssrc = ssrc;
68 68
     this.eventEmitter = new EventEmitter();
@@ -151,8 +151,8 @@ JitsiTrack.prototype.getUsageLabel = function () {
151 151
  * @private
152 152
  */
153 153
 JitsiTrack.prototype._maybeFireTrackAttached = function (container) {
154
-    if (this.rtc && container) {
155
-        this.rtc.eventEmitter.emit(RTCEvents.TRACK_ATTACHED, this, container);
154
+    if (this.conference && container) {
155
+        this.conference._onTrackAttach(this, container);
156 156
     }
157 157
 };
158 158
 

+ 49
- 69
modules/RTC/RTC.js Просмотреть файл

@@ -32,8 +32,8 @@ function createLocalTracks(tracksInfo, options) {
32 32
     return newTracks;
33 33
 }
34 34
 
35
-function RTC(room, options) {
36
-    this.room = room;
35
+function RTC(conference, options) {
36
+    this.conference = conference;
37 37
     this.localTracks = [];
38 38
     //FIXME: We should support multiple streams per jid.
39 39
     this.remoteTracks = {};
@@ -42,24 +42,6 @@ function RTC(room, options) {
42 42
     this.eventEmitter = new EventEmitter();
43 43
     var self = this;
44 44
     this.options = options || {};
45
-    room.addPresenceListener("videomuted", function (values, from) {
46
-        var videoTrack = self.getRemoteVideoTrack(from);
47
-        if (videoTrack) {
48
-            videoTrack.setMute(values.value == "true");
49
-        }
50
-    });
51
-    room.addPresenceListener("audiomuted", function (values, from) {
52
-        var audioTrack = self.getRemoteAudioTrack(from);
53
-        if (audioTrack) {
54
-            audioTrack.setMute(values.value == "true");
55
-        }
56
-    });
57
-    room.addPresenceListener("videoType", function(data, from) {
58
-        var videoTrack = self.getRemoteVideoTrack(from);
59
-        if (videoTrack) {
60
-            videoTrack._setVideoType(data.value);
61
-        }
62
-    });
63 45
 
64 46
     // Switch audio output device on all remote audio tracks. Local audio tracks
65 47
     // handle this event by themselves.
@@ -102,43 +84,6 @@ RTC.prototype.onIncommingCall = function(event) {
102 84
     if(this.options.config.openSctp)
103 85
         this.dataChannels = new DataChannels(event.peerconnection,
104 86
             this.eventEmitter);
105
-    // Add local Tracks to the ChatRoom
106
-    this.localTracks.forEach(function(localTrack) {
107
-        var ssrcInfo = null;
108
-        if(localTrack.isVideoTrack() && localTrack.isMuted()) {
109
-            /**
110
-             * Handles issues when the stream is added before the peerconnection
111
-             * is created. The peerconnection is created when second participant
112
-             * enters the call. In that use case the track doesn't have
113
-             * information about it's ssrcs and no jingle packets are sent. That
114
-             * can cause inconsistent behavior later.
115
-             *
116
-             * For example:
117
-             * If we mute the stream and than second participant enter it's
118
-             * remote SDP won't include that track. On unmute we are not sending
119
-             * any jingle packets which will brake the unmute.
120
-             *
121
-             * In order to solve issues like the above one here we have to
122
-             * generate the ssrc information for the track .
123
-             */
124
-            localTrack._setSSRC(
125
-                this.room.generateNewStreamSSRCInfo());
126
-            ssrcInfo = {
127
-                mtype: localTrack.getType(),
128
-                type: "addMuted",
129
-                ssrc: localTrack.ssrc,
130
-                msid: localTrack.initialMSID
131
-            };
132
-        }
133
-        try {
134
-            this.room.addStream(
135
-                localTrack.getOriginalStream(), function () {}, function () {},
136
-                ssrcInfo, true);
137
-        } catch(e) {
138
-            GlobalOnErrorHandler.callErrorHandler(e);
139
-            logger.error(e);
140
-        }
141
-    }.bind(this));
142 87
 };
143 88
 
144 89
 RTC.prototype.selectEndpoint = function (id) {
@@ -185,7 +130,8 @@ RTC.prototype.addLocalTrack = function (track) {
185 130
         throw new Error('track must not be null nor undefined');
186 131
 
187 132
     this.localTracks.push(track);
188
-    track._setRTC(this);
133
+
134
+    track.conference = this.conference;
189 135
 
190 136
     if (track.isAudioTrack()) {
191 137
         this.localAudio = track;
@@ -203,18 +149,29 @@ RTC.prototype.getLocalVideoTrack = function () {
203 149
 };
204 150
 
205 151
 /**
206
- * Gets JitsiRemoteTrack for AUDIO MediaType associated with given MUC nickname
207
- * (resource part of the JID).
152
+ * Gets JitsiRemoteTrack for the passed MediaType associated with given MUC
153
+ * nickname (resource part of the JID).
154
+ * @param type audio or video.
208 155
  * @param resource the resource part of the MUC JID
209 156
  * @returns {JitsiRemoteTrack|null}
210 157
  */
211
-RTC.prototype.getRemoteAudioTrack = function (resource) {
158
+RTC.prototype.getRemoteTrackByType = function (type, resource) {
212 159
     if (this.remoteTracks[resource])
213
-        return this.remoteTracks[resource][MediaType.AUDIO];
160
+        return this.remoteTracks[resource][type];
214 161
     else
215 162
         return null;
216 163
 };
217 164
 
165
+/**
166
+ * Gets JitsiRemoteTrack for AUDIO MediaType associated with given MUC nickname
167
+ * (resource part of the JID).
168
+ * @param resource the resource part of the MUC JID
169
+ * @returns {JitsiRemoteTrack|null}
170
+ */
171
+RTC.prototype.getRemoteAudioTrack = function (resource) {
172
+    return this.getRemoteTrackByType(MediaType.AUDIO, resource);
173
+};
174
+
218 175
 /**
219 176
  * Gets JitsiRemoteTrack for VIDEO MediaType associated with given MUC nickname
220 177
  * (resource part of the JID).
@@ -222,10 +179,7 @@ RTC.prototype.getRemoteAudioTrack = function (resource) {
222 179
  * @returns {JitsiRemoteTrack|null}
223 180
  */
224 181
 RTC.prototype.getRemoteVideoTrack = function (resource) {
225
-    if (this.remoteTracks[resource])
226
-        return this.remoteTracks[resource][MediaType.VIDEO];
227
-    else
228
-        return null;
182
+    return this.getRemoteTrackByType(MediaType.VIDEO, resource);
229 183
 };
230 184
 
231 185
 /**
@@ -272,7 +226,7 @@ RTC.prototype.removeLocalTrack = function (track) {
272 226
 RTC.prototype.createRemoteTrack = function (event) {
273 227
     var ownerJid = event.owner;
274 228
     var remoteTrack = new JitsiRemoteTrack(
275
-        this,  ownerJid, event.stream,    event.track,
229
+        this.conference,  ownerJid, event.stream,    event.track,
276 230
         event.mediaType, event.videoType, event.ssrc, event.muted);
277 231
     var resource = Strophe.getResourceFromJid(ownerJid);
278 232
     var remoteTracks
@@ -415,7 +369,8 @@ RTC.isDesktopSharingEnabled = function () {
415 369
  * Closes all currently opened data channels.
416 370
  */
417 371
 RTC.prototype.closeAllDataChannels = function () {
418
-    this.dataChannels.closeAllChannels();
372
+    if(this.dataChannels)
373
+        this.dataChannels.closeAllChannels();
419 374
 };
420 375
 
421 376
 RTC.prototype.dispose = function() {
@@ -457,7 +412,7 @@ RTC.prototype.setAudioLevel = function (resource, audioLevel) {
457 412
 RTC.prototype.getResourceBySSRC = function (ssrc) {
458 413
     if((this.localVideo && ssrc == this.localVideo.getSSRC())
459 414
         || (this.localAudio && ssrc == this.localAudio.getSSRC())) {
460
-        return Strophe.getResourceFromJid(this.room.myroomjid);
415
+        return this.conference.myUserId();
461 416
     }
462 417
 
463 418
     var track = this.getRemoteTrackBySSRC(ssrc);
@@ -482,4 +437,29 @@ RTC.prototype.getRemoteTrackBySSRC = function (ssrc) {
482 437
     return null;
483 438
 };
484 439
 
440
+/**
441
+ * Handles remote track mute / unmute events.
442
+ * @param type {string} "audio" or "video"
443
+ * @param isMuted {boolean} the new mute state
444
+ * @param from {string} user id
445
+ */
446
+RTC.prototype.handleRemoteTrackMute = function (type, isMuted, from) {
447
+    var track = this.getRemoteTrackByType(type, from);
448
+    if (track) {
449
+        track.setMute(isMuted);
450
+    }
451
+}
452
+
453
+/**
454
+ * Handles remote track video type events
455
+ * @param value {string} the new video type
456
+ * @param from {string} user id
457
+ */
458
+RTC.prototype.handleRemoteTrackVideoTypeChanged = function (value, from) {
459
+    var videoTrack = this.getRemoteVideoTrack(from);
460
+    if (videoTrack) {
461
+        videoTrack._setVideoType(value);
462
+    }
463
+}
464
+
485 465
 module.exports = RTC;

+ 8
- 0
modules/statistics/CallStats.js Просмотреть файл

@@ -473,4 +473,12 @@ CallStats.sendApplicationLog = _try_catch(function (e, cs) {
473 473
         .call(cs, wrtcFuncNames.applicationLog, e, null);
474 474
 });
475 475
 
476
+/**
477
+ * Clears allocated resources.
478
+ */
479
+CallStats.dispose = function () {
480
+    callStats = null;
481
+    CallStats.initialized = false;
482
+};
483
+
476 484
 module.exports = CallStats;

+ 24
- 4
modules/statistics/statistics.js Просмотреть файл

@@ -8,6 +8,11 @@ var CallStats = require("./CallStats");
8 8
 var ScriptUtil = require('../util/ScriptUtil');
9 9
 var JitsiTrackError = require("../../JitsiTrackError");
10 10
 
11
+/**
12
+ * True if callstats API is loaded
13
+ */
14
+ var isCallstatsLoaded = false;
15
+
11 16
 // Since callstats.io is a third party, we cannot guarantee the quality of their
12 17
 // service. More specifically, their server may take noticeably long time to
13 18
 // respond. Consequently, it is in our best interest (in the sense that the
@@ -16,10 +21,13 @@ var JitsiTrackError = require("../../JitsiTrackError");
16 21
 // downloading their API as soon as possible and (2) do the downloading
17 22
 // asynchronously.
18 23
 function loadCallStatsAPI() {
19
-    ScriptUtil.loadScript(
20
-            'https://api.callstats.io/static/callstats.min.js',
21
-            /* async */ true,
22
-            /* prepend */ true);
24
+    if(!isCallstatsLoaded) {
25
+        ScriptUtil.loadScript(
26
+                'https://api.callstats.io/static/callstats.min.js',
27
+                /* async */ true,
28
+                /* prepend */ true);
29
+        isCallstatsLoaded = true;
30
+    }
23 31
     // FIXME At the time of this writing, we hope that the callstats.io API will
24 32
     // have loaded by the time we needed it (i.e. CallStats.init is invoked).
25 33
 }
@@ -210,6 +218,18 @@ Statistics.prototype.startCallStats = function (session, settings) {
210 218
     }
211 219
 };
212 220
 
221
+/**
222
+ * Removes the callstats.io instances.
223
+ */
224
+Statistics.prototype.stopCallStats = function () {
225
+    if(this.callstats) {
226
+        var index = Statistics.callsStatsInstances.indexOf(this.callstats);
227
+        Statistics.callsStatsInstances.splice(index, 1);
228
+        this.callstats = null;
229
+        CallStats.dispose();
230
+    }
231
+};
232
+
213 233
 /**
214 234
  * Returns true if the callstats integration is enabled, otherwise returns
215 235
  * false.

+ 37
- 0
modules/util/EventEmitterForwarder.js Просмотреть файл

@@ -0,0 +1,37 @@
1
+var EventEmitter = require("events");
2
+
3
+/**
4
+ * Implements utility to forward events from one eventEmitter to another.
5
+ * @param src {object} instance of EventEmitter or another class that implements
6
+ * addListener method which will register listener to EventEmitter instance.
7
+ * @param dest {object} instance of EventEmitter or another class that
8
+ * implements emit method which will emit an event.
9
+ */
10
+function EventEmitterForwarder (src, dest) {
11
+    if (!src || !dest || typeof(src.addListener) !== "function" ||
12
+        typeof(dest.emit) !== "function")
13
+        throw new Error("Invalid arguments passed to EventEmitterForwarder");
14
+    this.src = src;
15
+    this.dest = dest;
16
+}
17
+
18
+/**
19
+ * Adds event to be forwarded from src to dest.
20
+ * @param srcEvent {string} the event that EventEmitterForwarder is listening
21
+ * for.
22
+ * @param dstEvent {string} the event that will be fired from dest.
23
+ * @param arguments all other passed arguments are going to be fired with
24
+ * dstEvent.
25
+ */
26
+EventEmitterForwarder.prototype.forward = function () {
27
+    // This line is only for fixing jshint errors.
28
+    var args = arguments;
29
+    var srcEvent = args[0];
30
+    //This will be the "this" value for emit function.
31
+    args[0] = this.dest;
32
+    //Using bind.apply to pass the arguments as Array-like object ("arguments")
33
+    this.src.addListener(srcEvent,
34
+        Function.prototype.bind.apply(this.dest.emit, args));
35
+};
36
+
37
+module.exports = EventEmitterForwarder;

+ 14
- 8
modules/version/ComponentsVersions.js Просмотреть файл

@@ -19,17 +19,18 @@ ComponentsVersions.XMPP_SERVER_COMPONENT = "xmpp";
19 19
 
20 20
 /**
21 21
  * Creates new instance of <tt>ComponentsVersions</tt> which will be discovering
22
- * the versions of conferencing system components in given <tt>ChatRoom</tt>.
23
- * @param chatRoom <tt>ChatRoom</tt> instance which will be used to listen for
24
- *        focus presence updates.
22
+ * the versions of conferencing system components in given
23
+ * <tt>JitsiConference</tt>.
24
+ * @param conference <tt>JitsiConference</tt> instance which will be used to
25
+ *        listen for focus presence updates.
25 26
  * @constructor
26 27
  */
27
-function ComponentsVersions(chatRoom) {
28
+function ComponentsVersions(conference) {
28 29
 
29 30
     this.versions = {};
30 31
 
31
-    this.chatRoom = chatRoom;
32
-    this.chatRoom.addPresenceListener(
32
+    this.conference = conference;
33
+    this.conference.addCommandListener(
33 34
         'versions', this.processPresence.bind(this));
34 35
 }
35 36
 
@@ -41,7 +42,7 @@ function(node, mucResource, mucJid) {
41 42
         return;
42 43
     }
43 44
 
44
-    if (!this.chatRoom.isFocus(mucJid)) {
45
+    if (!this.conference._isFocus(mucJid)) {
45 46
         logger.warn(
46 47
             "Received versions not from the focus user: " + node, mucJid);
47 48
         return;
@@ -62,6 +63,12 @@ function(node, mucResource, mucJid) {
62 63
 
63 64
         var version = item.value;
64 65
         if (this.versions[componentName] !== version) {
66
+            if(this.versions[componentName] &&
67
+                componentName !== ComponentsVersions.FOCUS_COMPONENT &&
68
+                componentName !== ComponentsVersions.VIDEOBRIDGE_COMPONENT) {
69
+                //version is changed during the call
70
+                this.conference._fireIncompatibleVersionsEvent();
71
+            }
65 72
             this.versions[componentName] = version;
66 73
             logger.info("Got " + componentName + " version: " + version);
67 74
 
@@ -87,4 +94,3 @@ ComponentsVersions.prototype.getComponentVersion = function(componentName) {
87 94
 };
88 95
 
89 96
 module.exports = ComponentsVersions;
90
-

+ 64
- 17
modules/xmpp/ChatRoom.js Просмотреть файл

@@ -62,7 +62,8 @@ function filterNodeFromPresenceJSON(pres, nodeName){
62 62
     return res;
63 63
 }
64 64
 
65
-function ChatRoom(connection, jid, password, XMPP, options, settings) {
65
+function ChatRoom(connection, jid, password, XMPP, options, settings,
66
+    maxRetries) {
66 67
     this.eventEmitter = new EventEmitter();
67 68
     this.xmpp = XMPP;
68 69
     this.connection = connection;
@@ -74,12 +75,13 @@ function ChatRoom(connection, jid, password, XMPP, options, settings) {
74 75
     this.presMap = {};
75 76
     this.presHandlers = {};
76 77
     this.joined = false;
77
-    this.role = 'none';
78
+    this.role = null;
78 79
     this.focusMucJid = null;
79 80
     this.bridgeIsDown = false;
80 81
     this.options = options || {};
81 82
     this.moderator = new Moderator(this.roomjid, this.xmpp, this.eventEmitter,
82
-        settings, {connection: this.xmpp.options, conference: this.options});
83
+        settings, {connection: this.xmpp.options, conference: this.options},
84
+        maxRetries);
83 85
     this.initPresenceMap();
84 86
     this.session = null;
85 87
     var self = this;
@@ -243,7 +245,6 @@ ChatRoom.prototype.onPresence = function (pres) {
243 245
     member.jid = jid;
244 246
     member.isFocus
245 247
         = jid && jid.indexOf(this.moderator.getFocusUserJid() + "/") === 0;
246
-
247 248
     member.isHiddenDomain
248 249
         = jid && jid.indexOf("@") > 0
249 250
             && this.options.hiddenDomain
@@ -271,8 +272,9 @@ ChatRoom.prototype.onPresence = function (pres) {
271 272
     }
272 273
 
273 274
     if (from == this.myroomjid) {
274
-        if (member.affiliation == 'owner' && this.role !== member.role) {
275
-            this.role = member.role;
275
+        var newRole = member.affiliation == "owner"? member.role : "none";
276
+        if (this.role !== newRole) {
277
+            this.role = newRole;
276 278
             this.eventEmitter.emit(XMPPEvents.LOCAL_ROLE_CHANGED, this.role);
277 279
         }
278 280
         if (!this.joined) {
@@ -287,17 +289,8 @@ ChatRoom.prototype.onPresence = function (pres) {
287 289
         this.members[from] = member;
288 290
         logger.log('entered', from, member);
289 291
         if (member.isFocus) {
290
-            this.focusMucJid = from;
291
-            if(!this.recording) {
292
-                this.recording = new Recorder(this.options.recordingType,
293
-                    this.eventEmitter, this.connection, this.focusMucJid,
294
-                    this.options.jirecon, this.roomjid);
295
-                if(this.lastJibri)
296
-                    this.recording.handleJibriPresence(this.lastJibri);
297
-            }
298
-            logger.info("Ignore focus: " + from + ", real JID: " + jid);
299
-        }
300
-        else {
292
+            this._initFocus(from, jid);
293
+        } else {
301 294
             this.eventEmitter.emit(
302 295
                 XMPPEvents.MUC_MEMBER_JOINED,
303 296
                 from, member.nick, member.role, member.isHiddenDomain);
@@ -312,6 +305,19 @@ ChatRoom.prototype.onPresence = function (pres) {
312 305
                 XMPPEvents.MUC_ROLE_CHANGED, from, member.role);
313 306
         }
314 307
 
308
+        if (member.isFocus) {
309
+            // From time to time first few presences of the focus are not
310
+            // containing it's jid. That way we can mark later the focus member
311
+            // instead of not marking it at all and not starting the conference.
312
+            // FIXME: Maybe there is a better way to handle this issue. It seems
313
+            // there is some period of time in prosody that the configuration
314
+            // form is received but not applied. And if any participant joins
315
+            // during that period of time the first presence from the focus
316
+            // won't conain <item jid="focus..." />.
317
+            memberOfThis.isFocus = true;
318
+            this._initFocus(from, jid);
319
+        }
320
+
315 321
         // store the new display name
316 322
         if(member.displayName)
317 323
             memberOfThis.displayName = member.displayName;
@@ -370,6 +376,23 @@ ChatRoom.prototype.onPresence = function (pres) {
370 376
     }
371 377
 };
372 378
 
379
+/**
380
+ * Initialize some properties when the focus participant is verified.
381
+ * @param from jid of the focus
382
+ * @param mucJid the jid of the focus in the muc
383
+ */
384
+ChatRoom.prototype._initFocus = function (from, mucJid) {
385
+    this.focusMucJid = from;
386
+    if(!this.recording) {
387
+        this.recording = new Recorder(this.options.recordingType,
388
+            this.eventEmitter, this.connection, this.focusMucJid,
389
+            this.options.jirecon, this.roomjid);
390
+        if(this.lastJibri)
391
+            this.recording.handleJibriPresence(this.lastJibri);
392
+    }
393
+    logger.info("Ignore focus: " + from + ", real JID: " + mucJid);
394
+}
395
+
373 396
 /**
374 397
  * Sets the special listener to be used for "command"s whose name starts with
375 398
  * "jitsi_participant_".
@@ -601,6 +624,30 @@ ChatRoom.prototype.removePresenceListener = function (name) {
601 624
     delete this.presHandlers[name];
602 625
 };
603 626
 
627
+/**
628
+ * Exports the current state of the ChatRoom instance.
629
+ * @returns {object}
630
+ */
631
+ChatRoom.prototype.exportState = function () {
632
+    return {
633
+        presHandlers: this.presHandlers,
634
+        presMapNodes: this.presMap.nodes
635
+    }
636
+}
637
+
638
+/**
639
+ * Loads previously exported state object from ChatRoom instance into current
640
+ * ChatRoom instance.
641
+ * @param state {object} the state received by ChatRoom.exportState method.
642
+ */
643
+ChatRoom.prototype.loadState = function (state) {
644
+    if(!state || !state.presHandlers || !state.presMapNodes)
645
+        throw new Error("Invalid state object passed");
646
+
647
+    this.presHandlers = state.presHandlers;
648
+    this.presMap.nodes = state.presMapNodes;
649
+}
650
+
604 651
 /**
605 652
  * Checks if the user identified by given <tt>mucJid</tt> is the conference
606 653
  * focus.

+ 4
- 1
modules/xmpp/JingleSessionPC.js Просмотреть файл

@@ -1103,7 +1103,10 @@ JingleSessionPC.prototype.newJingleErrorHandler = function(request, failureCb) {
1103 1103
             error.source = request.tree();
1104 1104
         }
1105 1105
 
1106
-        error.session = this;
1106
+        // Commented to fix JSON.stringify(error) exception for circular
1107
+        // dependancies when we print that error.
1108
+        // FIXME: Maybe we can include part of the session object
1109
+        // error.session = this;
1107 1110
 
1108 1111
         logger.error("Jingle error", error);
1109 1112
         if (failureCb) {

+ 9
- 1
modules/xmpp/moderator.js Просмотреть файл

@@ -21,7 +21,7 @@ function createExpBackoffTimer(step) {
21 21
     };
22 22
 }
23 23
 
24
-function Moderator(roomName, xmpp, emitter, settings, options) {
24
+function Moderator(roomName, xmpp, emitter, settings, options, maxRetries) {
25 25
     this.roomName = roomName;
26 26
     this.xmppService = xmpp;
27 27
     this.getNextTimeout = createExpBackoffTimer(1000);
@@ -30,6 +30,8 @@ function Moderator(roomName, xmpp, emitter, settings, options) {
30 30
     this.externalAuthEnabled = false;
31 31
     this.settings = settings;
32 32
     this.options = options;
33
+    this.maxRetries = maxRetries || Infinity;
34
+    this.retries = 0;
33 35
 
34 36
     // Sip gateway can be enabled by configuring Jigasi host in config.js or
35 37
     // it will be enabled automatically if focus detects the component through
@@ -369,6 +371,12 @@ Moderator.prototype._allocateConferenceFocusError = function (error, callback) {
369 371
                 });
370 372
         return;
371 373
     }
374
+    if(this.retries >= this.maxRetries) {
375
+        self.eventEmitter.emit(
376
+                XMPPEvents.ALLOCATE_FOCUS_MAX_RETRIES_ERROR);
377
+        return;
378
+    }
379
+    this.retries++;
372 380
     var waitMs = self.getNextErrorTimeout();
373 381
     var errmsg = "Focus error, retry after "+ waitMs;
374 382
     GlobalOnErrorHandler.callErrorHandler(new Error(errmsg));

+ 2
- 2
modules/xmpp/strophe.emuc.js Просмотреть файл

@@ -24,7 +24,7 @@ module.exports = function(XMPP) {
24 24
             this.connection.addHandler(this.onMute.bind(this),
25 25
                 'http://jitsi.org/jitmeet/audio', 'iq', 'set',null,null);
26 26
         },
27
-        createRoom: function (jid, password, options, settings) {
27
+        createRoom: function (jid, password, options, settings, maxRetries) {
28 28
             var roomJid = Strophe.getBareJidFromJid(jid);
29 29
             if (this.rooms[roomJid]) {
30 30
                 var errmsg = "You are already in the room!";
@@ -33,7 +33,7 @@ module.exports = function(XMPP) {
33 33
                 return;
34 34
             }
35 35
             this.rooms[roomJid] = new ChatRoom(this.connection, jid,
36
-                password, XMPP, options, settings);
36
+                password, XMPP, options, settings, maxRetries);
37 37
             return this.rooms[roomJid];
38 38
         },
39 39
         doLeave: function (jid) {

+ 37
- 2
modules/xmpp/xmpp.js Просмотреть файл

@@ -42,6 +42,8 @@ function XMPP(options, token) {
42 42
     this.connectionTimes = {};
43 43
     this.forceMuted = false;
44 44
     this.options = options;
45
+    this.connectParams = {};
46
+    this.token = token;
45 47
     initStrophePlugins(this);
46 48
 
47 49
     this.connection = createConnection(options.bosh, token);
@@ -57,6 +59,34 @@ function XMPP(options, token) {
57 59
     $(window).on('beforeunload unload', this.disconnect.bind(this));
58 60
 }
59 61
 
62
+/**
63
+ * Reloads the XMPP module
64
+ */
65
+XMPP.prototype.reload = function () {
66
+    this.disconnect();
67
+    this.connection.pause();
68
+    this.connection = createConnection(this.options.bosh, this.token);
69
+
70
+    // Initialize features advertised in disco-info
71
+    this.initFeaturesList();
72
+
73
+    //getData for attach
74
+    if(this.options.prebindURL &&
75
+        typeof(createConnectionExternally) === "function") {
76
+        var self = this;
77
+        createConnectionExternally(this.options.prebindURL, function (data) {
78
+            self.attach(data);
79
+        }, function (error) {
80
+            //connect
81
+            self.connect(this.connectParams.jid, this.connectParams.password);
82
+        });
83
+    } else {
84
+        //connect
85
+        this.connect(this.connectParams.jid, this.connectParams.password);
86
+    }
87
+
88
+}
89
+
60 90
 /**
61 91
  * Initializes the list of feature advertised through the disco-info mechanism
62 92
  */
@@ -219,6 +249,10 @@ XMPP.prototype._connect = function (jid, password) {
219 249
 }
220 250
 
221 251
 XMPP.prototype.connect = function (jid, password) {
252
+    this.connectParams = {
253
+        jid: jid,
254
+        password: password
255
+    };
222 256
     if (!jid) {
223 257
         var configDomain
224 258
             = this.options.hosts.anonymousdomain || this.options.hosts.domain;
@@ -234,7 +268,7 @@ XMPP.prototype.connect = function (jid, password) {
234 268
     return this._connect(jid, password);
235 269
 };
236 270
 
237
-XMPP.prototype.createRoom = function (roomName, options, settings) {
271
+XMPP.prototype.createRoom = function (roomName, options, settings, maxRetries) {
238 272
     var roomjid = roomName  + '@' + this.options.hosts.muc;
239 273
 
240 274
     if (options.useNicks) {
@@ -254,7 +288,8 @@ XMPP.prototype.createRoom = function (roomName, options, settings) {
254 288
         roomjid += '/' + tmpJid;
255 289
     }
256 290
 
257
-    return this.connection.emuc.createRoom(roomjid, null, options, settings);
291
+    return this.connection.emuc.createRoom(roomjid, null, options, settings,
292
+        maxRetries);
258 293
 }
259 294
 
260 295
 XMPP.prototype.addListener = function(type, listener) {

+ 5
- 0
service/xmpp/XMPPEvents.js Просмотреть файл

@@ -7,6 +7,11 @@ var XMPPEvents = {
7 7
     // audio.
8 8
     AUDIO_MUTED_BY_FOCUS: "xmpp.audio_muted_by_focus",
9 9
     AUTHENTICATION_REQUIRED: "xmpp.authentication_required",
10
+    /**
11
+     * Max retries value of Moderator.allocateConferenceFocus failures is
12
+     * reached.
13
+     */
14
+    ALLOCATE_FOCUS_MAX_RETRIES_ERROR: "xmpp.allocate_focus_max_retries_error",
10 15
     BRIDGE_DOWN: "xmpp.bridge_down",
11 16
     // Designates an event indicating that an offer (e.g. Jingle
12 17
     // session-initiate) was received.

Загрузка…
Отмена
Сохранить