瀏覽代碼

Refactors RTC module to support multiple conferences. Implements JitsiTrack interfaces.

dev1
hristoterezov 10 年之前
父節點
當前提交
2328443ecb

+ 25
- 8
JitsiConference.js 查看文件

@@ -1,5 +1,8 @@
1 1
 var RTC = require("./modules/RTC/RTC");
2 2
 var XMPPEvents = require("./service/xmpp/XMPPEvents");
3
+var StreamEventTypes = require("./service/RTC/StreamEventTypes");
4
+var EventEmitter = require("events");
5
+var JitsiConferenceEvents = require("./JitsiConferenceEvents");
3 6
 
4 7
 /**
5 8
  * Creates a JitsiConference object with the given name and properties.
@@ -15,11 +18,10 @@ function JitsiConference(options) {
15 18
     this.options = options;
16 19
     this.connection = this.options.connection;
17 20
     this.xmpp = this.connection.xmpp;
18
-    this.room = this.xmpp.createRoom(this.options.name, null, null);
19
-    this.rtc = new RTC();
20
-    this.xmpp.addListener(XMPPEvents.CALL_INCOMING,
21
-        this.rtc.onIncommingCall.bind(this.rtc));
22
-
21
+    this.eventEmitter = new EventEmitter();
22
+    this.room = this.xmpp.createRoom(this.options.name, null, null, this.options.config);
23
+    this.rtc = new RTC(this.room, options);
24
+    setupListeners(this);
23 25
 }
24 26
 
25 27
 /**
@@ -27,7 +29,6 @@ function JitsiConference(options) {
27 29
  * @param password {string} the password
28 30
  */
29 31
 JitsiConference.prototype.join = function (password) {
30
-
31 32
     this.room.join(password);
32 33
 }
33 34
 
@@ -67,7 +68,7 @@ JitsiConference.prototype.getLocalTracks = function () {
67 68
  * Note: consider adding eventing functionality by extending an EventEmitter impl, instead of rolling ourselves
68 69
  */
69 70
 JitsiConference.prototype.on = function (eventId, handler) {
70
-    this.room.addListener(eventId, handler);
71
+    this.eventEmitter.on(eventId, handler);
71 72
 }
72 73
 
73 74
 /**
@@ -78,7 +79,7 @@ JitsiConference.prototype.on = function (eventId, handler) {
78 79
  * Note: consider adding eventing functionality by extending an EventEmitter impl, instead of rolling ourselves
79 80
  */
80 81
 JitsiConference.prototype.off = function (eventId, handler) {
81
-    this.room.removeListener(eventId, listener);
82
+    this.eventEmitter.removeListener(eventId, listener);
82 83
 }
83 84
 
84 85
 // Common aliases for event emitter
@@ -181,5 +182,21 @@ JitsiConference.prototype.getParticipantById = function(id) {
181 182
 
182 183
 }
183 184
 
185
+function setupListeners(conference) {
186
+    conference.xmpp.addListener(XMPPEvents.CALL_INCOMING,
187
+        conference.rtc.onIncommingCall.bind(conference.rtc));
188
+    conference.room.addListener(XMPPEvents.REMOTE_STREAM_RECEIVED,
189
+        conference.rtc.createRemoteStream.bind(conference.rtc));
190
+    conference.rtc.addListener(StreamEventTypes.EVENT_TYPE_REMOTE_CREATED, function (stream) {
191
+        conference.eventEmitter.emit(JitsiConferenceEvents.TRACK_ADDED, stream);
192
+    });
193
+    conference.rtc.addListener(StreamEventTypes.EVENT_TYPE_REMOTE_CREATED, function (stream) {
194
+        conference.eventEmitter.emit(JitsiConferenceEvents.TRACK_REMOVED, stream);
195
+    })
196
+    conference.room.addListener(XMPPEvents.MUC_JOINED, function () {
197
+        conference.eventEmitter.emit(JitsiConferenceEvents.CONFERENCE_JOINED);
198
+    })
199
+}
200
+
184 201
 
185 202
 module.exports = JitsiConference;

+ 3
- 0
JitsiMeetJS.js 查看文件

@@ -17,6 +17,9 @@ var LibJitsiMeet = {
17 17
     errors: {
18 18
         conference: JitsiConferenceErrors,
19 19
         connection: JitsiConnectionErrors
20
+    },
21
+    init: function (options) {
22
+        require("./modules/RTC/RTC").init(options || {});
20 23
     }
21 24
 
22 25
 }

+ 6097
- 5557
lib-jitsi-meet.js
文件差異過大導致無法顯示
查看文件


+ 136
- 143
modules/RTC/DataChannels.js 查看文件

@@ -4,155 +4,148 @@
4 4
 // https://code.google.com/p/chromium/issues/detail?id=405545
5 5
 var RTCEvents = require("../../service/RTC/RTCEvents");
6 6
 
7
-var _dataChannels = [];
8
-var eventEmitter = null;
9
-
10
-
11
-var DataChannels = {
12
-    /**
13
-     * Callback triggered by PeerConnection when new data channel is opened
14
-     * on the bridge.
15
-     * @param event the event info object.
16
-     */
17
-    onDataChannel: function (event) {
18
-        var dataChannel = event.channel;
19
-
20
-        dataChannel.onopen = function () {
21
-            console.info("Data channel opened by the Videobridge!", dataChannel);
22
-
23
-            // Code sample for sending string and/or binary data
24
-            // Sends String message to the bridge
25
-            //dataChannel.send("Hello bridge!");
26
-            // Sends 12 bytes binary message to the bridge
27
-            //dataChannel.send(new ArrayBuffer(12));
28
-
29
-            eventEmitter.emit(RTCEvents.DATA_CHANNEL_OPEN);
30
-        };
31
-
32
-        dataChannel.onerror = function (error) {
33
-            console.error("Data Channel Error:", error, dataChannel);
34
-        };
35
-
36
-        dataChannel.onmessage = function (event) {
37
-            var data = event.data;
38
-            // JSON
39
-            var obj;
40
-
41
-            try {
42
-                obj = JSON.parse(data);
43
-            }
44
-            catch (e) {
45
-                console.error(
46
-                    "Failed to parse data channel message as JSON: ",
47
-                    data,
48
-                    dataChannel);
49
-            }
50
-            if (('undefined' !== typeof(obj)) && (null !== obj)) {
51
-                var colibriClass = obj.colibriClass;
52 7
 
53
-                if ("DominantSpeakerEndpointChangeEvent" === colibriClass) {
54
-                    // Endpoint ID from the Videobridge.
55
-                    var dominantSpeakerEndpoint = obj.dominantSpeakerEndpoint;
8
+/**
9
+ * Binds "ondatachannel" event listener to given PeerConnection instance.
10
+ * @param peerConnection WebRTC peer connection instance.
11
+ */
12
+function DataChannels(peerConnection, emitter) {
13
+    peerConnection.ondatachannel = this.onDataChannel;
14
+    this.eventEmitter = emitter;
15
+
16
+    this._dataChannels = [];
17
+
18
+    // Sample code for opening new data channel from Jitsi Meet to the bridge.
19
+    // Although it's not a requirement to open separate channels from both bridge
20
+    // and peer as single channel can be used for sending and receiving data.
21
+    // So either channel opened by the bridge or the one opened here is enough
22
+    // for communication with the bridge.
23
+    /*var dataChannelOptions =
24
+     {
25
+     reliable: true
26
+     };
27
+     var dataChannel
28
+     = peerConnection.createDataChannel("myChannel", dataChannelOptions);
29
+
30
+     // Can be used only when is in open state
31
+     dataChannel.onopen = function ()
32
+     {
33
+     dataChannel.send("My channel !!!");
34
+     };
35
+     dataChannel.onmessage = function (event)
36
+     {
37
+     var msgData = event.data;
38
+     console.info("Got My Data Channel Message:", msgData, dataChannel);
39
+     };*/
40
+};
56 41
 
57
-                    console.info(
58
-                        "Data channel new dominant speaker event: ",
59
-                        dominantSpeakerEndpoint);
60
-                    eventEmitter.emit(RTCEvents.DOMINANTSPEAKER_CHANGED, dominantSpeakerEndpoint);
61
-                }
62
-                else if ("InLastNChangeEvent" === colibriClass) {
63
-                    var oldValue = obj.oldValue;
64
-                    var newValue = obj.newValue;
65
-                    // Make sure that oldValue and newValue are of type boolean.
66
-                    var type;
67
-
68
-                    if ((type = typeof oldValue) !== 'boolean') {
69
-                        if (type === 'string') {
70
-                            oldValue = (oldValue == "true");
71
-                        } else {
72
-                            oldValue = new Boolean(oldValue).valueOf();
73
-                        }
74
-                    }
75
-                    if ((type = typeof newValue) !== 'boolean') {
76
-                        if (type === 'string') {
77
-                            newValue = (newValue == "true");
78
-                        } else {
79
-                            newValue = new Boolean(newValue).valueOf();
80
-                        }
81
-                    }
82 42
 
83
-                    eventEmitter.emit(RTCEvents.LASTN_CHANGED, oldValue, newValue);
84
-                }
85
-                else if ("LastNEndpointsChangeEvent" === colibriClass) {
86
-                    // The new/latest list of last-n endpoint IDs.
87
-                    var lastNEndpoints = obj.lastNEndpoints;
88
-                    // The list of endpoint IDs which are entering the list of
89
-                    // last-n at this time i.e. were not in the old list of last-n
90
-                    // endpoint IDs.
91
-                    var endpointsEnteringLastN = obj.endpointsEnteringLastN;
92
-
93
-                    console.log(
94
-                        "Data channel new last-n event: ",
95
-                        lastNEndpoints, endpointsEnteringLastN, obj);
96
-                    eventEmitter.emit(RTCEvents.LASTN_ENDPOINT_CHANGED,
97
-                        lastNEndpoints, endpointsEnteringLastN, obj);
43
+/**
44
+ * Callback triggered by PeerConnection when new data channel is opened
45
+ * on the bridge.
46
+ * @param event the event info object.
47
+ */
48
+DataChannels.prototype.onDataChannel = function (event) {
49
+    var dataChannel = event.channel;
50
+
51
+    dataChannel.onopen = function () {
52
+        console.info("Data channel opened by the Videobridge!", dataChannel);
53
+
54
+        // Code sample for sending string and/or binary data
55
+        // Sends String message to the bridge
56
+        //dataChannel.send("Hello bridge!");
57
+        // Sends 12 bytes binary message to the bridge
58
+        //dataChannel.send(new ArrayBuffer(12));
59
+
60
+        this.eventEmitter.emit(RTCEvents.DATA_CHANNEL_OPEN);
61
+    }.bind(this);
62
+
63
+    dataChannel.onerror = function (error) {
64
+        console.error("Data Channel Error:", error, dataChannel);
65
+    };
66
+
67
+    dataChannel.onmessage = function (event) {
68
+        var data = event.data;
69
+        // JSON
70
+        var obj;
71
+
72
+        try {
73
+            obj = JSON.parse(data);
74
+        }
75
+        catch (e) {
76
+            console.error(
77
+                "Failed to parse data channel message as JSON: ",
78
+                data,
79
+                dataChannel);
80
+        }
81
+        if (('undefined' !== typeof(obj)) && (null !== obj)) {
82
+            var colibriClass = obj.colibriClass;
83
+
84
+            if ("DominantSpeakerEndpointChangeEvent" === colibriClass) {
85
+                // Endpoint ID from the Videobridge.
86
+                var dominantSpeakerEndpoint = obj.dominantSpeakerEndpoint;
87
+
88
+                console.info(
89
+                    "Data channel new dominant speaker event: ",
90
+                    dominantSpeakerEndpoint);
91
+                this.eventEmitter.emit(RTCEvents.DOMINANTSPEAKER_CHANGED, dominantSpeakerEndpoint);
92
+            }
93
+            else if ("InLastNChangeEvent" === colibriClass) {
94
+                var oldValue = obj.oldValue;
95
+                var newValue = obj.newValue;
96
+                // Make sure that oldValue and newValue are of type boolean.
97
+                var type;
98
+
99
+                if ((type = typeof oldValue) !== 'boolean') {
100
+                    if (type === 'string') {
101
+                        oldValue = (oldValue == "true");
102
+                    } else {
103
+                        oldValue = new Boolean(oldValue).valueOf();
104
+                    }
98 105
                 }
99
-                else {
100
-                    console.debug("Data channel JSON-formatted message: ", obj);
106
+                if ((type = typeof newValue) !== 'boolean') {
107
+                    if (type === 'string') {
108
+                        newValue = (newValue == "true");
109
+                    } else {
110
+                        newValue = new Boolean(newValue).valueOf();
111
+                    }
101 112
                 }
113
+
114
+                this.eventEmitter.emit(RTCEvents.LASTN_CHANGED, oldValue, newValue);
115
+            }
116
+            else if ("LastNEndpointsChangeEvent" === colibriClass) {
117
+                // The new/latest list of last-n endpoint IDs.
118
+                var lastNEndpoints = obj.lastNEndpoints;
119
+                // The list of endpoint IDs which are entering the list of
120
+                // last-n at this time i.e. were not in the old list of last-n
121
+                // endpoint IDs.
122
+                var endpointsEnteringLastN = obj.endpointsEnteringLastN;
123
+
124
+                console.log(
125
+                    "Data channel new last-n event: ",
126
+                    lastNEndpoints, endpointsEnteringLastN, obj);
127
+                this.eventEmitter.emit(RTCEvents.LASTN_ENDPOINT_CHANGED,
128
+                    lastNEndpoints, endpointsEnteringLastN, obj);
129
+            }
130
+            else {
131
+                console.debug("Data channel JSON-formatted message: ", obj);
102 132
             }
103
-        };
104
-
105
-        dataChannel.onclose = function () {
106
-            console.info("The Data Channel closed", dataChannel);
107
-            var idx = _dataChannels.indexOf(dataChannel);
108
-            if (idx > -1)
109
-                _dataChannels = _dataChannels.splice(idx, 1);
110
-        };
111
-        _dataChannels.push(dataChannel);
112
-    },
113
-
114
-    /**
115
-     * Binds "ondatachannel" event listener to given PeerConnection instance.
116
-     * @param peerConnection WebRTC peer connection instance.
117
-     */
118
-    init: function (peerConnection, emitter) {
119
-        if(!config.openSctp)
120
-            return;
121
-
122
-        peerConnection.ondatachannel = this.onDataChannel;
123
-        eventEmitter = emitter;
124
-
125
-        // Sample code for opening new data channel from Jitsi Meet to the bridge.
126
-        // Although it's not a requirement to open separate channels from both bridge
127
-        // and peer as single channel can be used for sending and receiving data.
128
-        // So either channel opened by the bridge or the one opened here is enough
129
-        // for communication with the bridge.
130
-        /*var dataChannelOptions =
131
-         {
132
-         reliable: true
133
-         };
134
-         var dataChannel
135
-         = peerConnection.createDataChannel("myChannel", dataChannelOptions);
136
-
137
-         // Can be used only when is in open state
138
-         dataChannel.onopen = function ()
139
-         {
140
-         dataChannel.send("My channel !!!");
141
-         };
142
-         dataChannel.onmessage = function (event)
143
-         {
144
-         var msgData = event.data;
145
-         console.info("Got My Data Channel Message:", msgData, dataChannel);
146
-         };*/
147
-    },
148
-    handleSelectedEndpointEvent: onSelectedEndpointChanged,
149
-    handlePinnedEndpointEvent: onPinnedEndpointChanged
133
+        }
134
+    }.bind(this);
135
+
136
+    dataChannel.onclose = function () {
137
+        console.info("The Data Channel closed", dataChannel);
138
+        var idx = this._dataChannels.indexOf(dataChannel);
139
+        if (idx > -1)
140
+            this._dataChannels = this._dataChannels.splice(idx, 1);
141
+    }.bind(this);
142
+    this._dataChannels.push(dataChannel);
150 143
 };
151 144
 
152
-function onSelectedEndpointChanged(userResource) {
145
+DataChannels.prototype.handleSelectedEndpointEvent = function (userResource) {
153 146
     console.log('selected endpoint changed: ', userResource);
154
-    if (_dataChannels && _dataChannels.length != 0) {
155
-        _dataChannels.some(function (dataChannel) {
147
+    if (this._dataChannels && this._dataChannels.length != 0) {
148
+        this._dataChannels.some(function (dataChannel) {
156 149
             if (dataChannel.readyState == 'open') {
157 150
                 console.log('sending selected endpoint changed ' +
158 151
                     'notification to the bridge: ', userResource);
@@ -169,10 +162,10 @@ function onSelectedEndpointChanged(userResource) {
169 162
     }
170 163
 }
171 164
 
172
-function onPinnedEndpointChanged(userResource) {
165
+DataChannels.prototype.handlePinnedEndpointEvent = function (userResource) {
173 166
     console.log('pinned endpoint changed: ', userResource);
174
-    if (_dataChannels && _dataChannels.length != 0) {
175
-        _dataChannels.some(function (dataChannel) {
167
+    if (this._dataChannels && this._dataChannels.length != 0) {
168
+        this._dataChannels.some(function (dataChannel) {
176 169
             if (dataChannel.readyState == 'open') {
177 170
                 dataChannel.send(JSON.stringify({
178 171
                     'colibriClass': 'PinnedEndpointChangedEvent',

+ 119
- 0
modules/RTC/JitsiLocalTrack.js 查看文件

@@ -0,0 +1,119 @@
1
+var JitsiTrack = require("./JitsiTrack");
2
+var StreamEventTypes = require("../../service/RTC/StreamEventTypes");
3
+var RTCEvents = require("../../service/RTC/RTCEvents");
4
+var RTCBrowserType = require("./RTCBrowserType");
5
+
6
+/**
7
+ * This implements 'onended' callback normally fired by WebRTC after the stream
8
+ * is stopped. There is no such behaviour yet in FF, so we have to add it.
9
+ * @param stream original WebRTC stream object to which 'onended' handling
10
+ *               will be added.
11
+ */
12
+function implementOnEndedHandling(stream) {
13
+    var originalStop = stream.stop;
14
+    stream.stop = function () {
15
+        originalStop.apply(stream);
16
+        if (!stream.ended) {
17
+            stream.ended = true;
18
+            stream.onended();
19
+        }
20
+    };
21
+}
22
+
23
+/**
24
+ * Represents a single media track (either audio or video).
25
+ * @constructor
26
+ */
27
+function JitsiLocalTrack(RTC, stream, eventEmitter, videoType, isGUMStream)
28
+{
29
+    JitsiTrack.call(this, RTC, stream);
30
+    this.eventEmitter = eventEmitter;
31
+    this.videoType = videoType;
32
+    this.isGUMStream = true;
33
+    if(isGUMStream === false)
34
+        this.isGUMStream = isGUMStream;
35
+    this.stream.onended = function () {
36
+        this.eventEmitter.emit(StreamEventTypes.EVENT_TYPE_LOCAL_ENDED, this);
37
+    }.bind(this);
38
+    if (RTCBrowserType.isFirefox()) {
39
+        implementOnEndedHandling(this.stream);
40
+    }
41
+}
42
+
43
+JitsiLocalTrack.prototype = Object.create(JitsiTrack.prototype);
44
+JitsiLocalTrack.prototype.constructor = JitsiLocalTrack;
45
+
46
+/**
47
+ * Mutes / unmutes the track.
48
+ * @param mute {boolean} if true the track will be muted. Otherwise the track will be unmuted.
49
+ */
50
+JitsiLocalTrack.prototype._setMute = function (mute) {
51
+    var isAudio = this.type === JitsiTrack.AUDIO;
52
+    var eventType = isAudio ? RTCEvents.AUDIO_MUTE : RTCEvents.VIDEO_MUTE;
53
+
54
+    if ((window.location.protocol != "https:" && this.isGUMStream) ||
55
+        (isAudio && this.isGUMStream) || this.videoType === "screen" ||
56
+        // FIXME FF does not support 'removeStream' method used to mute
57
+        RTCBrowserType.isFirefox()) {
58
+
59
+        var tracks = this.getTracks();
60
+        for (var idx = 0; idx < tracks.length; idx++) {
61
+            tracks[idx].enabled = !mute;
62
+        }
63
+        if(isAudio)
64
+            this.rtc.room.setAudioMute(mute);
65
+        else
66
+            this.rtc.room.setVideoMute(mute);
67
+        this.eventEmitter.emit(eventType, mute);
68
+    } else {
69
+        if (mute) {
70
+            this.rtc.room.removeStream(this.stream);
71
+            this.stream.stop();
72
+            if(isAudio)
73
+                this.rtc.room.setAudioMute(mute);
74
+            else
75
+                this.rtc.room.setVideoMute(mute);
76
+            this.eventEmitter.emit(eventType, true);
77
+        } else {
78
+            var self = this;
79
+            this.rtc.obtainAudioAndVideoPermissions(
80
+                {devices: (this.isAudioStream() ? ["audio"] : ["video"])})
81
+                .then(function (stream) {
82
+                    if (isAudio) {
83
+                        self.rtc.changeLocalAudio(stream,
84
+                            function () {
85
+                                this.rtc.room.setAudioMute(mute);
86
+                                self.eventEmitter.emit(eventType, false);
87
+                            });
88
+                    } else {
89
+                        self.rtc.changeLocalVideo(stream, false,
90
+                            function () {
91
+                                this.rtc.room.setVideoMute(mute);
92
+                                self.eventEmitter.emit(eventType, false);
93
+                            });
94
+                    }
95
+                });
96
+        }
97
+    }
98
+}
99
+
100
+/**
101
+ * Stops sending the media track. And removes it from the HTML.
102
+ * NOTE: Works for local tracks only.
103
+ */
104
+JitsiLocalTrack.prototype.stop = function () {
105
+    this.rtc.room.removeStream(this.stream);
106
+    this.stream.stop();
107
+    this.detach();
108
+}
109
+
110
+
111
+/**
112
+ * Starts sending the track.
113
+ * NOTE: Works for local tracks only.
114
+ */
115
+JitsiLocalTrack.prototype.start = function() {
116
+    this.rtc.room.addStream(this.stream, function () {});
117
+}
118
+
119
+module.exports = JitsiLocalTrack;

+ 31
- 0
modules/RTC/JitsiRemoteTrack.js 查看文件

@@ -0,0 +1,31 @@
1
+var JitsiTrack = require("./JitsiTrack");
2
+
3
+/**
4
+ * Represents a single media track (either audio or video).
5
+ * @constructor
6
+ */
7
+function JitsiRemoteTrack(RTC, data, sid, ssrc, browser, eventEmitter) {
8
+    JitsiTrack.call(this, RTC, data.stream);
9
+    this.rtc = RTC;
10
+    this.sid = sid;
11
+    this.stream = data.stream;
12
+    this.peerjid = data.peerjid;
13
+    this.videoType = data.videoType;
14
+    this.ssrc = ssrc;
15
+    this.muted = false;
16
+    this.eventEmitter = eventEmitter;
17
+}
18
+
19
+JitsiRemoteTrack.prototype = Object.create(JitsiTrack.prototype);
20
+JitsiRemoteTrack.prototype.constructor = JitsiRemoteTrack;
21
+
22
+JitsiRemoteTrack.prototype._setMute = function (value) {
23
+    this.stream.muted = value;
24
+    this.muted = value;
25
+};
26
+
27
+delete JitsiRemoteTrack.prototype.stop;
28
+
29
+delete JitsiRemoteTrack.prototype.start;
30
+
31
+module.exports = JitsiRemoteTrack;

JitsiTrack.js → modules/RTC/JitsiTrack.js 查看文件

@@ -1,10 +1,24 @@
1
+var RTC = require("./RTCUtils");
2
+
1 3
 /**
2 4
  * Represents a single media track (either audio or video).
3 5
  * @constructor
4 6
  */
5
-function JitsiTrack(stream)
7
+function JitsiTrack(RTC, stream)
6 8
 {
9
+    this.rtc = RTC;
7 10
     this.stream = stream;
11
+    this.type = (this.stream.getVideoTracks().length > 0)?
12
+        JitsiTrack.VIDEO : JitsiTrack.AUDIO;
13
+    if(this.type == "audio") {
14
+        this._getTracks = function () {
15
+            return this.stream.getAudioTracks();
16
+        }.bind(this);
17
+    } else {
18
+        this._getTracks = function () {
19
+            return this.stream.getVideoTracks();
20
+        }.bind(this);
21
+    }
8 22
 }
9 23
 
10 24
 /**
@@ -23,7 +37,7 @@ JitsiTrack.AUDIO = "audio";
23 37
  * Returns the type (audio or video) of this track.
24 38
  */
25 39
 JitsiTrack.prototype.getType = function() {
26
-    return this.stream.type;
40
+    return this.type;
27 41
 };
28 42
 
29 43
 /**
@@ -37,21 +51,21 @@ JitsiTrack.prototype.getParitcipant = function() {
37 51
  * Returns the RTCMediaStream from the browser (?).
38 52
  */
39 53
 JitsiTrack.prototype.getOriginalStream = function() {
40
-    return this.stream.getOriginalStream();
54
+    return this.stream;
41 55
 }
42 56
 
43 57
 /**
44 58
  * Mutes the track.
45 59
  */
46 60
 JitsiTrack.prototype.mute = function () {
47
-    this.stream.setMute(true);
61
+    this._setMute(true);
48 62
 }
49 63
 
50 64
 /**
51 65
  * Unmutes the stream.
52 66
  */
53 67
 JitsiTrack.prototype.unmute = function () {
54
-    this.stream.setMute(false);
68
+    this._setMute(false);
55 69
 }
56 70
 
57 71
 /**
@@ -59,7 +73,7 @@ JitsiTrack.prototype.unmute = function () {
59 73
  * @param container the HTML container
60 74
  */
61 75
 JitsiTrack.prototype.attach = function (container) {
62
-
76
+    RTC.attachMediaStream(container, this.stream);
63 77
 }
64 78
 
65 79
 /**
@@ -67,7 +81,7 @@ JitsiTrack.prototype.attach = function (container) {
67 81
  * @param container the HTML container
68 82
  */
69 83
 JitsiTrack.prototype.detach = function (container) {
70
-
84
+    $(container).find(">video").remove();
71 85
 }
72 86
 
73 87
 /**
@@ -76,6 +90,7 @@ JitsiTrack.prototype.detach = function (container) {
76 90
  */
77 91
 JitsiTrack.prototype.stop = function () {
78 92
 
93
+    this.detach();
79 94
 }
80 95
 
81 96
 
@@ -84,6 +99,7 @@ JitsiTrack.prototype.stop = function () {
84 99
  * NOTE: Works for local tracks only.
85 100
  */
86 101
 JitsiTrack.prototype.start = function() {
102
+
87 103
 }
88 104
 
89 105
 /**
@@ -91,6 +107,18 @@ JitsiTrack.prototype.start = function() {
91 107
  * screen capture as opposed to a camera.
92 108
  */
93 109
 JitsiTrack.prototype.isScreenSharing = function(){
110
+
94 111
 }
95 112
 
113
+/**
114
+ * Returns id of the track.
115
+ * @returns {string} id of the track or null if this is fake track.
116
+ */
117
+JitsiTrack.prototype.getId = function () {
118
+    var tracks = this.stream.getTracks();
119
+    if(!tracks || tracks.length === 0)
120
+        return null;
121
+    return tracks[0].id;
122
+};
123
+
96 124
 module.exports = JitsiTrack;

+ 58
- 31
modules/RTC/RTC.js 查看文件

@@ -2,9 +2,9 @@
2 2
 var EventEmitter = require("events");
3 3
 var RTCBrowserType = require("./RTCBrowserType");
4 4
 var RTCUtils = require("./RTCUtils.js");
5
-var LocalStream = require("./LocalStream.js");
5
+var JitsiLocalTrack = require("./JitsiLocalTrack.js");
6 6
 var DataChannels = require("./DataChannels");
7
-var MediaStream = require("./MediaStream.js");
7
+var JitsiRemoteTrack = require("./JitsiRemoteTrack.js");
8 8
 var DesktopSharingEventTypes
9 9
     = require("../../service/desktopsharing/DesktopSharingEventTypes");
10 10
 var MediaStreamType = require("../../service/RTC/MediaStreamTypes");
@@ -40,13 +40,16 @@ function getMediaStreamUsage()
40 40
     return result;
41 41
 }
42 42
 
43
+var rtcReady = false;
43 44
 
44
-function RTC(options)
45
-{
45
+
46
+
47
+function RTC(room, options) {
46 48
     this.devices = {
47 49
         audio: true,
48 50
         video: true
49 51
     };
52
+    this.room = room;
50 53
     this.localStreams = [];
51 54
     this.remoteStreams = {};
52 55
     this.localAudio = null;
@@ -59,19 +62,6 @@ function RTC(options)
59 62
             self.changeLocalVideo(stream, isUsingScreenStream, callback);
60 63
         }, DesktopSharingEventTypes.NEW_STREAM_CREATED);
61 64
 
62
-    // In case of IE we continue from 'onReady' callback
63
-    // passed to RTCUtils constructor. It will be invoked by Temasys plugin
64
-    // once it is initialized.
65
-    var onReady = function () {
66
-        self.eventEmitter.emit(RTCEvents.RTC_READY, true);
67
-    };
68
-
69
-    RTCUtils.init(onReady);
70
-
71
-    // Call onReady() if Temasys plugin is not used
72
-    if (!RTCBrowserType.isTemasysPluginUsed()) {
73
-        onReady();
74
-    }
75 65
 }
76 66
 
77 67
 RTC.prototype.obtainAudioAndVideoPermissions = function (options) {
@@ -80,15 +70,19 @@ RTC.prototype.obtainAudioAndVideoPermissions = function (options) {
80 70
 }
81 71
 
82 72
 RTC.prototype.onIncommingCall = function(event) {
83
-    DataChannels.init(event.peerconnection, self.eventEmitter);
73
+    if(this.options.config.openSctp)
74
+        this.dataChannels = new DataChannels(event.peerconnection, this.eventEmitter);
75
+    this.room.addLocalStreams(this.localStreams);
84 76
 }
85 77
 
86 78
 RTC.prototype.selectedEndpoint = function (id) {
87
-    DataChannels.handleSelectedEndpointEvent(id);
79
+    if(this.dataChannels)
80
+        this.dataChannels.handleSelectedEndpointEvent(id);
88 81
 }
89 82
 
90 83
 RTC.prototype.pinEndpoint = function (id) {
91
-    DataChannels.handlePinnedEndpointEvent(id);
84
+    if(this.dataChannels)
85
+        this.dataChannels.handlePinnedEndpointEvent(id);
92 86
 }
93 87
 
94 88
 RTC.prototype.addStreamListener = function (listener, eventType) {
@@ -99,17 +93,50 @@ RTC.prototype.addListener = function (type, listener) {
99 93
     this.eventEmitter.on(type, listener);
100 94
 };
101 95
 
96
+RTC.prototype.removeListener = function (listener, eventType) {
97
+    this.eventEmitter.removeListener(eventType, listener);
98
+};
99
+
102 100
 RTC.prototype.removeStreamListener = function (listener, eventType) {
103 101
     if(!(eventType instanceof StreamEventTypes))
104 102
         throw "Illegal argument";
105 103
 
106
-    this.removeListener(eventType, listener);
104
+    this.eventEmitter.removeListener(eventType, listener);
107 105
 };
108 106
 
107
+RTC.addRTCReadyListener = function (listener) {
108
+    RTCUtils.eventEmitter.on(RTCEvents.RTC_READY, listener);
109
+}
110
+
111
+RTC.removeRTCReadyListener = function (listener) {
112
+    RTCUtils.eventEmitter.removeListener(RTCEvents.RTC_READY, listener);
113
+}
114
+
115
+RTC.isRTCReady = function () {
116
+    return rtcReady;
117
+}
118
+
119
+RTC.init = function (options) {
120
+    // In case of IE we continue from 'onReady' callback
121
+// passed to RTCUtils constructor. It will be invoked by Temasys plugin
122
+// once it is initialized.
123
+    var onReady = function () {
124
+        rtcReady = true;
125
+        RTCUtils.eventEmitter.emit(RTCEvents.RTC_READY, true);
126
+    };
127
+
128
+    RTCUtils.init(onReady, options || {});
129
+
130
+// Call onReady() if Temasys plugin is not used
131
+    if (!RTCBrowserType.isTemasysPluginUsed()) {
132
+        onReady();
133
+    }
134
+}
135
+
109 136
 RTC.prototype.createLocalStreams = function (streams, change) {
110 137
     for (var i = 0; i < streams.length; i++) {
111
-        var localStream = new LocalStream(this, streams[i].stream,
112
-            streams[i].type, this.eventEmitter, streams[i].videoType,
138
+        var localStream = new JitsiLocalTrack(this, streams[i].stream,
139
+            this.eventEmitter, streams[i].videoType,
113 140
             streams[i].isGUMStream);
114 141
         this.localStreams.push(localStream);
115 142
         if (streams[i].isMuted === true)
@@ -139,9 +166,9 @@ RTC.prototype.removeLocalStream = function (stream) {
139 166
 };
140 167
 
141 168
 RTC.prototype.createRemoteStream = function (data, sid, thessrc) {
142
-    var remoteStream = new MediaStream(data, sid, thessrc,
169
+    var remoteStream = new JitsiRemoteTrack(this, data, sid, thessrc,
143 170
         RTCBrowserType.getBrowserType(), this.eventEmitter);
144
-    if(data.peerjid)
171
+    if(!data.peerjid)
145 172
         return;
146 173
     var jid = data.peerjid;
147 174
     if(!this.remoteStreams[jid]) {
@@ -223,9 +250,9 @@ RTC.prototype.changeLocalVideo = function (stream, isUsingScreenStream, callback
223 250
 
224 251
     if(this.localVideo.isMuted() && this.localVideo.videoType !== type) {
225 252
         localCallback = function() {
226
-            APP.xmpp.setVideoMute(false, function(mute) {
227
-                self.eventEmitter.emit(RTCEvents.VIDEO_MUTE, mute);
228
-            });
253
+            this.room.setVideoMute(false, function(mute) {
254
+                this.eventEmitter.emit(RTCEvents.VIDEO_MUTE, mute);
255
+            }.bind(this));
229 256
             
230 257
             callback();
231 258
         };
@@ -241,7 +268,7 @@ RTC.prototype.changeLocalVideo = function (stream, isUsingScreenStream, callback
241 268
 
242 269
     this.switchVideoStreams(videoStream, oldStream);
243 270
 
244
-    APP.xmpp.switchStreams(videoStream, oldStream,localCallback);
271
+    this.room.switchStreams(videoStream, oldStream,localCallback);
245 272
 };
246 273
 
247 274
 RTC.prototype.changeLocalAudio = function (stream, callback) {
@@ -250,7 +277,7 @@ RTC.prototype.changeLocalAudio = function (stream, callback) {
250 277
     this.localAudio = this.createLocalStream(newStream, "audio", true);
251 278
     // Stop the stream to trigger onended event for old stream
252 279
     oldStream.stop();
253
-    APP.xmpp.switchStreams(newStream, oldStream, callback, true);
280
+    this.room.switchStreams(newStream, oldStream, callback, true);
254 281
 };
255 282
 
256 283
 RTC.prototype.isVideoMuted = function (jid) {
@@ -279,7 +306,7 @@ RTC.prototype.setVideoMute = function (mute, callback, options) {
279 306
     else
280 307
     {
281 308
         this.localVideo.setMute(mute);
282
-        APP.xmpp.setVideoMute(
309
+        this.room.setVideoMute(
283 310
             mute,
284 311
             callback,
285 312
             options);

+ 12
- 11
modules/RTC/RTCUtils.js 查看文件

@@ -3,8 +3,8 @@ var RTCBrowserType = require("./RTCBrowserType");
3 3
 var Resolutions = require("../../service/RTC/Resolutions");
4 4
 var AdapterJS = require("./adapter.screenshare");
5 5
 var SDPUtil = require("../xmpp/SDPUtil");
6
+var EventEmitter = require("events");
6 7
 
7
-var currentResolution = null;
8 8
 function DummyMediaStream(id) {
9 9
     this.id = id;
10 10
     this.label = id;
@@ -143,7 +143,8 @@ function getConstraints(um, resolution, bandwidth, fps, desktopStream, isAndroid
143 143
 
144 144
 //Options parameter is to pass config options. Currently uses only "useIPv6".
145 145
 var RTCUtils = {
146
-    init: function (onTemasysPluginReady) {
146
+    eventEmitter: new EventEmitter(),
147
+    init: function (onTemasysPluginReady, options) {
147 148
         var self = this;
148 149
         if (RTCBrowserType.isFirefox()) {
149 150
             var FFversion = RTCBrowserType.getFirefoxVersion();
@@ -217,7 +218,7 @@ var RTCUtils = {
217 218
             this.pc_constraints = {'optional': [
218 219
                 {'DtlsSrtpKeyAgreement': 'true'}
219 220
             ]};
220
-            if (this.service.options.useIPv6) {
221
+            if (options.useIPv6) {
221 222
                 // https://code.google.com/p/webrtc/issues/detail?id=2828
222 223
                 this.pc_constraints.optional.push({googIPv6: true});
223 224
             }
@@ -351,7 +352,7 @@ var RTCUtils = {
351 352
 
352 353
         return new Promise(function (resolve, reject) {
353 354
             var successCallback = function (stream) {
354
-                resolve(self.successCallback(stream, usageOptions));
355
+                resolve(self.successCallback(RTC , stream, usageOptions));
355 356
             };
356 357
 
357 358
             if (!devices)
@@ -436,13 +437,13 @@ var RTCUtils = {
436 437
         }.bind(this));
437 438
     },
438 439
 
439
-    successCallback: function (stream, usageOptions) {
440
+    successCallback: function (RTC, stream, usageOptions) {
440 441
         // If this is FF or IE, the stream parameter is *not* a MediaStream object,
441 442
         // it's an object with two properties: audioStream, videoStream.
442 443
         if (stream && stream.getAudioTracks && stream.getVideoTracks)
443 444
             console.log('got', stream, stream.getAudioTracks().length,
444 445
                 stream.getVideoTracks().length);
445
-        return this.handleLocalStream(stream, usageOptions);
446
+        return this.handleLocalStream(RTC, stream, usageOptions);
446 447
     },
447 448
 
448 449
     errorCallback: function (error, resolve, RTC, currentResolution) {
@@ -457,7 +458,7 @@ var RTCUtils = {
457 458
             && resolution != null) {
458 459
             self.getUserMediaWithConstraints(RTC, ['audio', 'video'],
459 460
                 function (stream) {
460
-                    resolve(self.successCallback(stream));
461
+                    resolve(self.successCallback(RTC, stream));
461 462
                 }, function (error, resolution) {
462 463
                     return self.errorCallback(error, resolve, RTC, resolution);
463 464
                 }, resolution);
@@ -467,18 +468,18 @@ var RTCUtils = {
467 468
                 RTC,
468 469
                 ['audio'],
469 470
                 function (stream) {
470
-                    resolve(self.successCallback(stream));
471
+                    resolve(self.successCallback(RTC, stream));
471 472
                 },
472 473
                 function (error) {
473 474
                     console.error('failed to obtain audio/video stream - stop',
474 475
                         error);
475
-                    resolve(self.successCallback(null));
476
+                    resolve(self.successCallback(RTC, null));
476 477
                 }
477 478
             );
478 479
         }
479 480
     },
480 481
 
481
-    handleLocalStream: function (stream, usageOptions) {
482
+    handleLocalStream: function (service, stream, usageOptions) {
482 483
         // If this is FF, the stream parameter is *not* a MediaStream object, it's
483 484
         // an object with two properties: audioStream, videoStream.
484 485
         if (window.webkitMediaStream) {
@@ -517,7 +518,7 @@ var RTCUtils = {
517 518
         var audioGUM = (!usageOptions || usageOptions.audio !== false),
518 519
             videoGUM = (!usageOptions || usageOptions.video !== false);
519 520
 
520
-        return this.service.createLocalStreams(
521
+        return service.createLocalStreams(
521 522
             [
522 523
                 {stream: audioStream, type: "audio", isMuted: audioMuted, isGUMStream: audioGUM, videoType: null},
523 524
                 {stream: videoStream, type: "video", isMuted: videoMuted, isGUMStream: videoGUM, videoType: "camera"}

+ 577
- 0
modules/xmpp/ChatRoom.js 查看文件

@@ -0,0 +1,577 @@
1
+var XMPPEvents = require("../../service/xmpp/XMPPEvents");
2
+var Moderator = require("./moderator");
3
+var EventEmitter = require("events");
4
+
5
+var parser = {
6
+    packet2JSON: function (packet, nodes) {
7
+        var self = this;
8
+        $(packet).children().each(function (index) {
9
+            var tagName = $(this).prop("tagName");
10
+            var node = {}
11
+            node["tagName"] = tagName;
12
+            node.attributes = {};
13
+            $($(this)[0].attributes).each(function( index, attr ) {
14
+                node.attributes[ attr.name ] = attr.value;
15
+            } );
16
+            var text = Strophe.getText($(this)[0]);
17
+            if(text)
18
+                node.value = text;
19
+            node.children = [];
20
+            nodes.push(node);
21
+            self.packet2JSON($(this), node.children);
22
+        })
23
+    },
24
+    JSON2packet: function (nodes, packet) {
25
+        for(var i = 0; i < nodes.length; i++)
26
+        {
27
+            var node = nodes[i];
28
+            if(!node || node === null){
29
+                continue;
30
+            }
31
+            packet.c(node.tagName, node.attributes);
32
+            if(node.value)
33
+                packet.t(node.value);
34
+            if(node.children)
35
+                this.JSON2packet(node.children, packet);
36
+            packet.up();
37
+        }
38
+        packet.up();
39
+    }
40
+};
41
+
42
+function ChatRoom(connection, jid, password, XMPP, options)
43
+{
44
+    this.eventEmitter = new EventEmitter();
45
+    this.xmpp = XMPP;
46
+    this.connection = connection;
47
+    this.roomjid = Strophe.getBareJidFromJid(jid);
48
+    this.myroomjid = jid;
49
+    this.password = password;
50
+    console.info("Joined MUC as " + this.myroomjid);
51
+    this.members = {};
52
+    this.presMap = {};
53
+    this.presHandlers = {};
54
+    this.joined = false;
55
+    this.role = null;
56
+    this.focusMucJid = null;
57
+    this.bridgeIsDown = false;
58
+    this.options = options || {};
59
+    this.moderator = new Moderator(this.roomjid, this.xmpp, this.eventEmitter);
60
+    this.initPresenceMap();
61
+    this.session = null;
62
+    var self = this;
63
+}
64
+
65
+ChatRoom.prototype.initPresenceMap = function () {
66
+    this.presMap['to'] = this.myroomjid;
67
+    this.presMap['xns'] = 'http://jabber.org/protocol/muc';
68
+    this.presMap["nodes"] = [];
69
+    this.presMap["nodes"].push( {
70
+        "tagName": "user-agent",
71
+        "value": navigator.userAgent,
72
+        "attributes": {xmlns: 'http://jitsi.org/jitmeet/user-agent'}
73
+    });
74
+};
75
+
76
+ChatRoom.prototype.join = function (password) {
77
+    if(password)
78
+        this.password = password;
79
+    this.moderator.allocateConferenceFocus(function()
80
+    {
81
+        this.sendPresence();
82
+    }.bind(this));
83
+}
84
+
85
+ChatRoom.prototype.sendPresence = function () {
86
+    if (!this.presMap['to']) {
87
+        // Too early to send presence - not initialized
88
+        return;
89
+    }
90
+    var pres = $pres({to: this.presMap['to'] });
91
+    pres.c('x', {xmlns: this.presMap['xns']});
92
+
93
+    if (this.password) {
94
+        pres.c('password').t(this.password).up();
95
+    }
96
+
97
+    pres.up();
98
+
99
+    // Send XEP-0115 'c' stanza that contains our capabilities info
100
+    if (this.connection.caps) {
101
+        this.connection.caps.node = this.xmpp.options.clientNode;
102
+        pres.c('c', this.connection.caps.generateCapsAttrs()).up();
103
+    }
104
+
105
+    parser.JSON2packet(this.presMap.nodes, pres);
106
+    this.connection.send(pres);
107
+};
108
+
109
+
110
+ChatRoom.prototype.doLeave = function () {
111
+    console.log("do leave", this.myroomjid);
112
+    var pres = $pres({to: this.myroomjid, type: 'unavailable' });
113
+    this.presMap.length = 0;
114
+    this.connection.send(pres);
115
+};
116
+
117
+
118
+ChatRoom.prototype.createNonAnonymousRoom = function () {
119
+    // http://xmpp.org/extensions/xep-0045.html#createroom-reserved
120
+
121
+    var getForm = $iq({type: 'get', to: this.roomjid})
122
+        .c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'})
123
+        .c('x', {xmlns: 'jabber:x:data', type: 'submit'});
124
+
125
+    var self = this;
126
+
127
+    this.connection.sendIQ(getForm, function (form) {
128
+
129
+        if (!$(form).find(
130
+                '>query>x[xmlns="jabber:x:data"]' +
131
+                '>field[var="muc#roomconfig_whois"]').length) {
132
+
133
+            console.error('non-anonymous rooms not supported');
134
+            return;
135
+        }
136
+
137
+        var formSubmit = $iq({to: this.roomjid, type: 'set'})
138
+            .c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'});
139
+
140
+        formSubmit.c('x', {xmlns: 'jabber:x:data', type: 'submit'});
141
+
142
+        formSubmit.c('field', {'var': 'FORM_TYPE'})
143
+            .c('value')
144
+            .t('http://jabber.org/protocol/muc#roomconfig').up().up();
145
+
146
+        formSubmit.c('field', {'var': 'muc#roomconfig_whois'})
147
+            .c('value').t('anyone').up().up();
148
+
149
+        self.connection.sendIQ(formSubmit);
150
+
151
+    }, function (error) {
152
+        console.error("Error getting room configuration form");
153
+    });
154
+};
155
+
156
+ChatRoom.prototype.onPresence = function (pres) {
157
+    var from = pres.getAttribute('from');
158
+    // Parse roles.
159
+    var member = {};
160
+    member.show = $(pres).find('>show').text();
161
+    member.status = $(pres).find('>status').text();
162
+    var tmp = $(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>item');
163
+    member.affiliation = tmp.attr('affiliation');
164
+    member.role = tmp.attr('role');
165
+
166
+    // Focus recognition
167
+    member.jid = tmp.attr('jid');
168
+    member.isFocus = false;
169
+    if (member.jid
170
+        && member.jid.indexOf(this.moderator.getFocusUserJid() + "/") == 0) {
171
+        member.isFocus = true;
172
+    }
173
+
174
+    $(pres).find(">x").remove();
175
+    var nodes = [];
176
+    parser.packet2JSON(pres, nodes);
177
+    for(var i = 0; i < nodes.length; i++)
178
+    {
179
+        var node = nodes[i];
180
+        switch(node.tagName)
181
+        {
182
+            case "nick":
183
+                member.nick = node.value;
184
+                if(!member.isFocus) {
185
+                    var displayName = !this.xmpp.options.displayJids
186
+                        ? member.nick : Strophe.getResourceFromJid(from);
187
+
188
+                    if (displayName && displayName.length > 0) {
189
+                        this.eventEmitter.emit(XMPPEvents.DISPLAY_NAME_CHANGED, from, displayName);
190
+                    }
191
+                    console.info("Display name: " + displayName, pres);
192
+                }
193
+                break;
194
+            case "userId":
195
+                member.id = node.value;
196
+                break;
197
+            case "email":
198
+                member.email = node.value;
199
+                break;
200
+            case "bridgeIsDown":
201
+                if(!this.bridgeIsDown) {
202
+                    this.bridgeIsDown = true;
203
+                    this.eventEmitter.emit(XMPPEvents.BRIDGE_DOWN);
204
+                }
205
+                break;
206
+            default :
207
+                this.processNode(node, from);
208
+        }
209
+
210
+    }
211
+
212
+    if (from == this.myroomjid) {
213
+        if (member.affiliation == 'owner')
214
+
215
+            if (this.role !== member.role) {
216
+                this.role = member.role;
217
+
218
+                this.eventEmitter.emit(XMPPEvents.LOCAL_ROLE_CHANGED,
219
+                    member, this.isModerator());
220
+            }
221
+        if (!this.joined) {
222
+            this.joined = true;
223
+            this.eventEmitter.emit(XMPPEvents.MUC_JOINED, from, member);
224
+        }
225
+    } else if (this.members[from] === undefined) {
226
+        // new participant
227
+        this.members[from] = member;
228
+        console.log('entered', from, member);
229
+        if (member.isFocus) {
230
+            this.focusMucJid = from;
231
+            console.info("Ignore focus: " + from + ", real JID: " + member.jid);
232
+        }
233
+        else {
234
+            this.eventEmitter.emit(XMPPEvents.MUC_MEMBER_JOINED, from, member.id || member.email, member.nick);
235
+        }
236
+    } else {
237
+        // Presence update for existing participant
238
+        // Watch role change:
239
+        if (this.members[from].role != member.role) {
240
+            this.members[from].role = member.role;
241
+            this.eventEmitter.emit(XMPPEvents.MUC_ROLE_CHANGED,
242
+                member.role, member.nick);
243
+        }
244
+    }
245
+
246
+
247
+
248
+    if(!member.isFocus)
249
+        this.eventEmitter.emit(XMPPEvents.USER_ID_CHANGED, from, member.id || member.email);
250
+
251
+    // Trigger status message update
252
+    if (member.status) {
253
+        this.eventEmitter.emit(XMPPEvents.PRESENCE_STATUS, from, member);
254
+    }
255
+
256
+};
257
+
258
+ChatRoom.prototype.processNode = function (node, from) {
259
+    if(this.presHandlers[node.tagName])
260
+        this.presHandlers[node.tagName](node, from);
261
+};
262
+
263
+ChatRoom.prototype.sendMessage = function (body, nickname) {
264
+    var msg = $msg({to: this.roomjid, type: 'groupchat'});
265
+    msg.c('body', body).up();
266
+    if (nickname) {
267
+        msg.c('nick', {xmlns: 'http://jabber.org/protocol/nick'}).t(nickname).up().up();
268
+    }
269
+    this.connection.send(msg);
270
+    this.eventEmitter.emit(XMPPEvents.SENDING_CHAT_MESSAGE, body);
271
+};
272
+
273
+ChatRoom.prototype.setSubject = function (subject) {
274
+    var msg = $msg({to: this.roomjid, type: 'groupchat'});
275
+    msg.c('subject', subject);
276
+    this.connection.send(msg);
277
+    console.log("topic changed to " + subject);
278
+};
279
+
280
+
281
+ChatRoom.prototype.onParticipantLeft = function (jid) {
282
+
283
+    this.eventEmitter.emit(XMPPEvents.MUC_MEMBER_LEFT, jid);
284
+
285
+    this.moderator.onMucMemberLeft(jid);
286
+};
287
+
288
+ChatRoom.prototype.onPresenceUnavailable = function (pres, from) {
289
+    // room destroyed ?
290
+    if ($(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]' +
291
+        '>destroy').length) {
292
+        var reason;
293
+        var reasonSelect = $(pres).find(
294
+                '>x[xmlns="http://jabber.org/protocol/muc#user"]' +
295
+                '>destroy>reason');
296
+        if (reasonSelect.length) {
297
+            reason = reasonSelect.text();
298
+        }
299
+
300
+        this.xmpp.disposeConference(false);
301
+        this.eventEmitter.emit(XMPPEvents.MUC_DESTROYED, reason);
302
+        delete this.connection.emuc.rooms[Strophe.getBareJidFromJid(jid)];
303
+        return true;
304
+    }
305
+
306
+    // Status code 110 indicates that this notification is "self-presence".
307
+    if (!$(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="110"]').length) {
308
+        delete this.members[from];
309
+        this.onParticipantLeft(from);
310
+    }
311
+    // If the status code is 110 this means we're leaving and we would like
312
+    // to remove everyone else from our view, so we trigger the event.
313
+    else if (Object.keys(this.members).length > 1) {
314
+        for (var i in this.members) {
315
+            var member = this.members[i];
316
+            delete this.members[i];
317
+            this.onParticipantLeft(member);
318
+        }
319
+    }
320
+    if ($(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="307"]').length) {
321
+        if (this.myroomjid === from) {
322
+            this.xmpp.disposeConference(false);
323
+            this.eventEmitter.emit(XMPPEvents.KICKED);
324
+        }
325
+    }
326
+};
327
+
328
+ChatRoom.prototype.onMessage = function (msg, from) {
329
+    var nick =
330
+        $(msg).find('>nick[xmlns="http://jabber.org/protocol/nick"]')
331
+            .text() ||
332
+        Strophe.getResourceFromJid(from);
333
+
334
+    var txt = $(msg).find('>body').text();
335
+    var type = msg.getAttribute("type");
336
+    if (type == "error") {
337
+        this.eventEmitter.emit(XMPPEvents.CHAT_ERROR_RECEIVED,
338
+            $(msg).find('>text').text(), txt);
339
+        return true;
340
+    }
341
+
342
+    var subject = $(msg).find('>subject');
343
+    if (subject.length) {
344
+        var subjectText = subject.text();
345
+        if (subjectText || subjectText == "") {
346
+            this.eventEmitter.emit(XMPPEvents.SUBJECT_CHANGED, subjectText);
347
+            console.log("Subject is changed to " + subjectText);
348
+        }
349
+    }
350
+
351
+    // xep-0203 delay
352
+    var stamp = $(msg).find('>delay').attr('stamp');
353
+
354
+    if (!stamp) {
355
+        // or xep-0091 delay, UTC timestamp
356
+        stamp = $(msg).find('>[xmlns="jabber:x:delay"]').attr('stamp');
357
+
358
+        if (stamp) {
359
+            // the format is CCYYMMDDThh:mm:ss
360
+            var dateParts = stamp.match(/(\d{4})(\d{2})(\d{2}T\d{2}:\d{2}:\d{2})/);
361
+            stamp = dateParts[1] + "-" + dateParts[2] + "-" + dateParts[3] + "Z";
362
+        }
363
+    }
364
+
365
+    if (txt) {
366
+        console.log('chat', nick, txt);
367
+        this.eventEmitter.emit(XMPPEvents.MESSAGE_RECEIVED,
368
+            from, nick, txt, this.myroomjid, stamp);
369
+    }
370
+}
371
+
372
+ChatRoom.prototype.onPresenceError = function (pres, from) {
373
+    if ($(pres).find('>error[type="auth"]>not-authorized[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]').length) {
374
+        console.log('on password required', from);
375
+        this.eventEmitter.emit(XMPPEvents.PASSWORD_REQUIRED);
376
+    } else if ($(pres).find(
377
+        '>error[type="cancel"]>not-allowed[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]').length) {
378
+        var toDomain = Strophe.getDomainFromJid(pres.getAttribute('to'));
379
+        if (toDomain === this.xmpp.options.hosts.anonymousdomain) {
380
+            // enter the room by replying with 'not-authorized'. This would
381
+            // result in reconnection from authorized domain.
382
+            // We're either missing Jicofo/Prosody config for anonymous
383
+            // domains or something is wrong.
384
+            this.eventEmitter.emit(XMPPEvents.ROOM_JOIN_ERROR, pres);
385
+
386
+        } else {
387
+            console.warn('onPresError ', pres);
388
+            this.eventEmitter.emit(XMPPEvents.ROOM_CONNECT_ERROR, pres);
389
+        }
390
+    } else {
391
+        console.warn('onPresError ', pres);
392
+        this.eventEmitter.emit(XMPPEvents.ROOM_CONNECT_ERROR, pres);
393
+    }
394
+};
395
+
396
+ChatRoom.prototype.kick = function (jid) {
397
+    var kickIQ = $iq({to: this.roomjid, type: 'set'})
398
+        .c('query', {xmlns: 'http://jabber.org/protocol/muc#admin'})
399
+        .c('item', {nick: Strophe.getResourceFromJid(jid), role: 'none'})
400
+        .c('reason').t('You have been kicked.').up().up().up();
401
+
402
+    this.connection.sendIQ(
403
+        kickIQ,
404
+        function (result) {
405
+            console.log('Kick participant with jid: ', jid, result);
406
+        },
407
+        function (error) {
408
+            console.log('Kick participant error: ', error);
409
+        });
410
+};
411
+
412
+ChatRoom.prototype.lockRoom = function (key, onSuccess, onError, onNotSupported) {
413
+    //http://xmpp.org/extensions/xep-0045.html#roomconfig
414
+    var ob = this;
415
+    this.connection.sendIQ($iq({to: this.roomjid, type: 'get'}).c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'}),
416
+        function (res) {
417
+            if ($(res).find('>query>x[xmlns="jabber:x:data"]>field[var="muc#roomconfig_roomsecret"]').length) {
418
+                var formsubmit = $iq({to: ob.roomjid, type: 'set'}).c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'});
419
+                formsubmit.c('x', {xmlns: 'jabber:x:data', type: 'submit'});
420
+                formsubmit.c('field', {'var': 'FORM_TYPE'}).c('value').t('http://jabber.org/protocol/muc#roomconfig').up().up();
421
+                formsubmit.c('field', {'var': 'muc#roomconfig_roomsecret'}).c('value').t(key).up().up();
422
+                // Fixes a bug in prosody 0.9.+ https://code.google.com/p/lxmppd/issues/detail?id=373
423
+                formsubmit.c('field', {'var': 'muc#roomconfig_whois'}).c('value').t('anyone').up().up();
424
+                // FIXME: is muc#roomconfig_passwordprotectedroom required?
425
+                ob.connection.sendIQ(formsubmit,
426
+                    onSuccess,
427
+                    onError);
428
+            } else {
429
+                onNotSupported();
430
+            }
431
+        }, onError);
432
+};
433
+
434
+ChatRoom.prototype.addToPresence = function (key, values) {
435
+    values.tagName = key;
436
+    this.presMap["nodes"].push(values);
437
+};
438
+
439
+ChatRoom.prototype.removeFromPresence = function (key) {
440
+    for(var i = 0; i < this.presMap.nodes.length; i++)
441
+    {
442
+        if(key === this.presMap.nodes[i].tagName)
443
+            this.presMap.nodes.splice(i, 1);
444
+    }
445
+};
446
+
447
+ChatRoom.prototype.addPresenceListener = function (name, handler) {
448
+    this.presHandlers[name] = handler;
449
+}
450
+
451
+ChatRoom.prototype.removePresenceListener = function (name) {
452
+    delete this.presHandlers[name];
453
+}
454
+
455
+ChatRoom.prototype.isModerator = function (jid) {
456
+    return this.role === 'moderator';
457
+};
458
+
459
+ChatRoom.prototype.getMemberRole = function (peerJid) {
460
+    if (this.members[peerJid]) {
461
+        return this.members[peerJid].role;
462
+    }
463
+    return null;
464
+};
465
+
466
+ChatRoom.prototype.setJingleSession = function(session){
467
+    this.session = session;
468
+    this.session.room = this;
469
+};
470
+
471
+
472
+ChatRoom.prototype.removeStream = function (stream) {
473
+    if(!this.session)
474
+        return;
475
+    this.session.peerconnection.removeStream(stream)
476
+}
477
+
478
+ChatRoom.prototype.switchStreams = function (stream, oldStream, callback, isAudio) {
479
+    if(this.session) {
480
+        // FIXME: will block switchInProgress on true value in case of exception
481
+        this.session.switchStreams(stream, oldStream, callback, isAudio);
482
+    } else {
483
+        // We are done immediately
484
+        console.warn("No conference handler or conference not started yet");
485
+        callback();
486
+    }
487
+};
488
+
489
+ChatRoom.prototype.addStream = function (stream, callback) {
490
+    if(this.session) {
491
+        // FIXME: will block switchInProgress on true value in case of exception
492
+        this.session.addStream(stream, callback);
493
+    } else {
494
+        // We are done immediately
495
+        console.warn("No conference handler or conference not started yet");
496
+        callback();
497
+    }
498
+}
499
+
500
+ChatRoom.prototype.setVideoMute = function (mute, callback, options) {
501
+    var self = this;
502
+    var localCallback = function (mute) {
503
+        self.sendVideoInfoPresence(mute);
504
+        return callback(mute);
505
+    };
506
+
507
+    if(this.session)
508
+    {
509
+        this.session.setVideoMute(
510
+            mute, localCallback, options);
511
+    }
512
+    else {
513
+        localCallback(mute);
514
+    }
515
+
516
+};
517
+
518
+ChatRoom.prototype.setAudioMute = function (mute, callback) {
519
+    //This will be for remote streams only
520
+//    if (this.forceMuted && !mute) {
521
+//        console.info("Asking focus for unmute");
522
+//        this.connection.moderate.setMute(this.connection.emuc.myroomjid, mute);
523
+//        // FIXME: wait for result before resetting muted status
524
+//        this.forceMuted = false;
525
+//    }
526
+
527
+
528
+    return this.sendAudioInfoPresence(mute, callback);;
529
+};
530
+
531
+ChatRoom.prototype.addAudioInfoToPresence = function (mute) {
532
+    this.addToPresence("audiomuted",
533
+        {attributes:
534
+        {"audions": "http://jitsi.org/jitmeet/audio"},
535
+            value: mute.toString()});
536
+}
537
+
538
+ChatRoom.prototype.sendAudioInfoPresence = function(mute, callback) {
539
+    this.addAudioInfoToPresence(mute);
540
+    if(this.connection) {
541
+        this.sendPresence();
542
+    }
543
+    callback();
544
+};
545
+
546
+ChatRoom.prototype.addVideoInfoToPresence = function (mute) {
547
+    this.addToPresence("videomuted",
548
+        {attributes:
549
+        {"videons": "http://jitsi.org/jitmeet/video"},
550
+            value: mute.toString()});
551
+}
552
+
553
+
554
+ChatRoom.prototype.sendVideoInfoPresence = function (mute) {
555
+    this.addVideoInfoToPresence(mute);
556
+    if(!this.connection)
557
+        return;
558
+    this.sendPresence();
559
+};
560
+
561
+ChatRoom.prototype.addListener = function(type, listener) {
562
+    this.eventEmitter.on(type, listener);
563
+};
564
+
565
+ChatRoom.prototype.removeListener = function (type, listener) {
566
+    this.eventEmitter.removeListener(type, listener);
567
+};
568
+
569
+ChatRoom.prototype.fireRemoteStreamEvent = function(data, sid, thessrc) {
570
+    this.eventEmitter.emit(XMPPEvents.REMOTE_STREAM_RECEIVED, data, sid, thessrc);
571
+}
572
+
573
+ChatRoom.prototype.addLocalStreams = function (localStreams) {
574
+    this.session.addLocalStreams(localStreams);
575
+}
576
+
577
+module.exports = ChatRoom;

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

@@ -46,6 +46,9 @@ function JingleSession(me, sid, connection, service, eventEmitter) {
46 46
 
47 47
     // ICE servers config (RTCConfiguration?).
48 48
     this.ice_config = {};
49
+
50
+    // The chat room instance associated with the session.
51
+    this.room = null;
49 52
 }
50 53
 
51 54
 /**

+ 41
- 1
modules/xmpp/JingleSessionPC.js 查看文件

@@ -70,6 +70,7 @@ JingleSessionPC.prototype.setAnswer = function(answer) {
70 70
 JingleSessionPC.prototype.updateModifySourcesQueue = function() {
71 71
     var signalingState = this.peerconnection.signalingState;
72 72
     var iceConnectionState = this.peerconnection.iceConnectionState;
73
+    console.debug(signalingState + " + " + iceConnectionState);
73 74
     if (signalingState === 'stable' && iceConnectionState === 'connected') {
74 75
         this.modifySourcesQueue.resume();
75 76
     } else {
@@ -108,10 +109,13 @@ JingleSessionPC.prototype.doInitialize = function () {
108 109
     this.peerconnection.onremovestream = function (event) {
109 110
         // Remove the stream from remoteStreams
110 111
         // FIXME: remotestreamremoved.jingle not defined anywhere(unused)
112
+
111 113
         $(document).trigger('remotestreamremoved.jingle', [event, self.sid]);
112 114
     };
113 115
     this.peerconnection.onsignalingstatechange = function (event) {
116
+        console.debug("signaling state11");
114 117
         if (!(self && self.peerconnection)) return;
118
+        console.debug("signaling state222");
115 119
         self.updateModifySourcesQueue();
116 120
     };
117 121
     /**
@@ -122,7 +126,9 @@ JingleSessionPC.prototype.doInitialize = function () {
122 126
      * @param event the event containing information about the change
123 127
      */
124 128
     this.peerconnection.oniceconnectionstatechange = function (event) {
129
+        console.debug("ice state11");
125 130
         if (!(self && self.peerconnection)) return;
131
+        console.debug("ice state222");
126 132
         self.updateModifySourcesQueue();
127 133
         switch (self.peerconnection.iceConnectionState) {
128 134
             case 'connected':
@@ -1100,6 +1106,40 @@ JingleSessionPC.prototype.switchStreams = function (new_stream, oldStream, succe
1100 1106
     });
1101 1107
 };
1102 1108
 
1109
+/**
1110
+ * Adds streams.
1111
+ * @param stream new stream that will be added.
1112
+ * @param success_callback callback executed after successful stream addition.
1113
+ */
1114
+JingleSessionPC.prototype.addStream = function (stream, callback) {
1115
+
1116
+    // Remember SDP to figure out added/removed SSRCs
1117
+    var oldSdp = null;
1118
+    if(this.peerconnection) {
1119
+        if(this.peerconnection.localDescription) {
1120
+            oldSdp = new SDP(this.peerconnection.localDescription.sdp);
1121
+        }
1122
+        if(stream)
1123
+            this.peerconnection.addStream(stream);
1124
+    }
1125
+
1126
+    // Conference is not active
1127
+    if(!oldSdp || !this.peerconnection) {
1128
+        callback();
1129
+        return;
1130
+    }
1131
+
1132
+    self.modifySourcesQueue.push(function() {
1133
+        console.log('modify sources done');
1134
+
1135
+        callback();
1136
+
1137
+        var newSdp = new SDP(this.peerconnection.localDescription.sdp);
1138
+        console.log("SDPs", oldSdp, newSdp);
1139
+        this.notifyMySSRCUpdate(oldSdp, newSdp);
1140
+    });
1141
+}
1142
+
1103 1143
 /**
1104 1144
  * Figures out added/removed ssrcs and send update IQs.
1105 1145
  * @param old_sdp SDP object for old description.
@@ -1430,7 +1470,7 @@ JingleSessionPC.prototype.remoteStreamAdded = function (data, times) {
1430 1470
         }
1431 1471
     }
1432 1472
 
1433
-    RTC.createRemoteStream(data, this.sid, thessrc);
1473
+    this.room.fireRemoteStreamEvent(data, this.sid, thessrc);
1434 1474
 
1435 1475
     var isVideo = data.stream.getVideoTracks().length > 0;
1436 1476
     // an attempt to work around https://github.com/jitsi/jitmeet/issues/32

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

@@ -5,6 +5,7 @@ var SSRCReplacement = require("./LocalSSRCReplacement");
5 5
 
6 6
 function TraceablePeerConnection(ice_config, constraints, session) {
7 7
     var self = this;
8
+    this.session = session;
8 9
     var RTCPeerConnectionType = null;
9 10
     if (RTCBrowserType.isFirefox()) {
10 11
         RTCPeerConnectionType = mozRTCPeerConnection;
@@ -374,7 +375,7 @@ TraceablePeerConnection.prototype.createOffer
374 375
 
375 376
             offer = SSRCReplacement.mungeLocalVideoSSRC(offer);
376 377
 
377
-            if (config.enableSimulcast && self.simulcast.isSupported()) {
378
+            if (self.session.room.options.enableSimulcast && self.simulcast.isSupported()) {
378 379
                 offer = self.simulcast.mungeLocalDescription(offer);
379 380
                 self.trace('createOfferOnSuccess::postTransform (simulcast)', dumpSDP(offer));
380 381
             }
@@ -404,7 +405,7 @@ TraceablePeerConnection.prototype.createAnswer
404 405
             // munge local video SSRC
405 406
             answer = SSRCReplacement.mungeLocalVideoSSRC(answer);
406 407
 
407
-            if (config.enableSimulcast && self.simulcast.isSupported()) {
408
+            if (self.session.room.options.enableSimulcast && self.simulcast.isSupported()) {
408 409
                 answer = self.simulcast.mungeLocalDescription(answer);
409 410
                 self.trace('createAnswerOnSuccess::postTransform (simulcast)', dumpSDP(answer));
410 411
             }

+ 11
- 492
modules/xmpp/strophe.emuc.js 查看文件

@@ -2,496 +2,7 @@
2 2
 /* a simple MUC connection plugin
3 3
  * can only handle a single MUC room
4 4
  */
5
-var XMPPEvents = require("../../service/xmpp/XMPPEvents");
6
-var Moderator = require("./moderator");
7
-var RTC = require("../RTC/RTC");
8
-var EventEmitter = require("events");
9
-
10
-var parser = {
11
-    packet2JSON: function (packet, nodes) {
12
-        var self = this;
13
-        $(packet).children().each(function (index) {
14
-            var tagName = $(this).prop("tagName");
15
-            var node = {}
16
-            node["tagName"] = tagName;
17
-            node.attributes = {};
18
-            $($(this)[0].attributes).each(function( index, attr ) {
19
-                node.attributes[ attr.name ] = attr.value;
20
-            } );
21
-            var text = Strophe.getText($(this)[0]);
22
-            if(text)
23
-                node.value = text;
24
-            node.children = [];
25
-            nodes.push(node);
26
-            self.packet2JSON($(this), node.children);
27
-        })
28
-    },
29
-    JSON2packet: function (nodes, packet) {
30
-        for(var i = 0; i < nodes.length; i++)
31
-        {
32
-            var node = nodes[i];
33
-            if(!node || node === null){
34
-                continue;
35
-            }
36
-            packet.c(node.tagName, node.attributes);
37
-            if(node.value)
38
-                packet.t(node.value);
39
-            if(node.children)
40
-                this.JSON2packet(node.children, packet);
41
-            packet.up();
42
-        }
43
-        packet.up();
44
-    }
45
-};
46
-
47
-function ChatRoom(connection, jid, password, XMPP, eventEmitter)
48
-{
49
-    this.eventEmitter = eventEmitter;
50
-    this.roomEmitter = new EventEmitter();
51
-    this.xmpp = XMPP;
52
-    this.connection = connection;
53
-    this.roomjid = Strophe.getBareJidFromJid(jid);
54
-    this.myroomjid = jid;
55
-    this.password = password;
56
-    console.info("Joined MUC as " + this.myroomjid);
57
-    this.members = {};
58
-    this.presMap = {};
59
-    this.presHandlers = {};
60
-    this.joined = false;
61
-    this.role = null;
62
-    this.focusMucJid = null;
63
-    this.bridgeIsDown = false;
64
-    this.moderator = new Moderator(this.roomjid, this.xmpp, eventEmitter);
65
-    this.initPresenceMap();
66
-    this.readyToJoin = false;
67
-    this.joinRequested = false;
68
-    var self = this;
69
-    this.moderator.allocateConferenceFocus(function()
70
-    {
71
-        self.readyToJoin = true;
72
-        if(self.joinRequested)
73
-        {
74
-            self.join();
75
-        }
76
-    });
77
-}
78
-
79
-ChatRoom.prototype.initPresenceMap = function () {
80
-    this.presMap['to'] = this.myroomjid;
81
-    this.presMap['xns'] = 'http://jabber.org/protocol/muc';
82
-    this.presMap["nodes"] = [];
83
-    if (RTC.localAudio && RTC.localAudio.isMuted()) {
84
-        this.nodes.push({
85
-            tagName: "audiomuted",
86
-            attributes: {xmlns: "http://jitsi.org/jitmeet/audio"},
87
-            value: "true"});
88
-    }
89
-    if (RTC.localVideo && RTC.localVideo.isMuted()) {
90
-        this.nodes.push({
91
-            tagName: "videomuted",
92
-            attributes: {xmlns: "http://jitsi.org/jitmeet/video"},
93
-            value: "true"});
94
-    }
95
-    this.presMap["nodes"].push( {
96
-        "tagName": "user-agent",
97
-        "value": navigator.userAgent,
98
-        "attributes": {xmlns: 'http://jitsi.org/jitmeet/user-agent'}
99
-    });
100
-};
101
-
102
-ChatRoom.prototype.join = function (password) {
103
-    if(password)
104
-        this.password = password;
105
-    if(!this.readyToJoin)
106
-    {
107
-        this.joinRequested = true;
108
-        return;
109
-    }
110
-    this.joinRequested = false;
111
-    this.sendPresence();
112
-}
113
-
114
-ChatRoom.prototype.sendPresence = function () {
115
-    if (!this.presMap['to']) {
116
-        // Too early to send presence - not initialized
117
-        return;
118
-    }
119
-    var pres = $pres({to: this.presMap['to'] });
120
-    pres.c('x', {xmlns: this.presMap['xns']});
121
-
122
-    if (this.password) {
123
-        pres.c('password').t(this.password).up();
124
-    }
125
-
126
-    pres.up();
127
-
128
-    // Send XEP-0115 'c' stanza that contains our capabilities info
129
-    if (this.connection.caps) {
130
-        this.connection.caps.node = this.xmpp.options.clientNode;
131
-        pres.c('c', this.connection.caps.generateCapsAttrs()).up();
132
-    }
133
-
134
-    parser.JSON2packet(this.presMap.nodes, pres);
135
-    this.connection.send(pres);
136
-};
137
-
138
-
139
-ChatRoom.prototype.doLeave = function () {
140
-    console.log("do leave", this.myroomjid);
141
-    var pres = $pres({to: this.myroomjid, type: 'unavailable' });
142
-    this.presMap.length = 0;
143
-    this.connection.send(pres);
144
-};
145
-
146
-
147
-ChatRoom.prototype.createNonAnonymousRoom = function () {
148
-    // http://xmpp.org/extensions/xep-0045.html#createroom-reserved
149
-
150
-    var getForm = $iq({type: 'get', to: this.roomjid})
151
-        .c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'})
152
-        .c('x', {xmlns: 'jabber:x:data', type: 'submit'});
153
-
154
-    var self = this;
155
-
156
-    this.connection.sendIQ(getForm, function (form) {
157
-
158
-        if (!$(form).find(
159
-                '>query>x[xmlns="jabber:x:data"]' +
160
-                '>field[var="muc#roomconfig_whois"]').length) {
161
-
162
-            console.error('non-anonymous rooms not supported');
163
-            return;
164
-        }
165
-
166
-        var formSubmit = $iq({to: this.roomjid, type: 'set'})
167
-            .c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'});
168
-
169
-        formSubmit.c('x', {xmlns: 'jabber:x:data', type: 'submit'});
170
-
171
-        formSubmit.c('field', {'var': 'FORM_TYPE'})
172
-            .c('value')
173
-            .t('http://jabber.org/protocol/muc#roomconfig').up().up();
174
-
175
-        formSubmit.c('field', {'var': 'muc#roomconfig_whois'})
176
-            .c('value').t('anyone').up().up();
177
-
178
-        self.connection.sendIQ(formSubmit);
179
-
180
-    }, function (error) {
181
-        console.error("Error getting room configuration form");
182
-    });
183
-};
184
-
185
-ChatRoom.prototype.onPresence = function (pres) {
186
-    var from = pres.getAttribute('from');
187
-    // Parse roles.
188
-    var member = {};
189
-    member.show = $(pres).find('>show').text();
190
-    member.status = $(pres).find('>status').text();
191
-    var tmp = $(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>item');
192
-    member.affiliation = tmp.attr('affiliation');
193
-    member.role = tmp.attr('role');
194
-
195
-    // Focus recognition
196
-    member.jid = tmp.attr('jid');
197
-    member.isFocus = false;
198
-    if (member.jid
199
-        && member.jid.indexOf(this.moderator.getFocusUserJid() + "/") == 0) {
200
-        member.isFocus = true;
201
-    }
202
-
203
-    $(pres).find(">x").remove();
204
-    var nodes = [];
205
-    parser.packet2JSON(pres, nodes);
206
-    for(var i = 0; i < nodes.length; i++)
207
-    {
208
-        var node = nodes[i];
209
-        switch(node.tagName)
210
-        {
211
-            case "nick":
212
-                member.nick = node.value;
213
-                if(!member.isFocus) {
214
-                    var displayName = !this.xmpp.options.displayJids
215
-                        ? member.nick : Strophe.getResourceFromJid(from);
216
-
217
-                    if (displayName && displayName.length > 0) {
218
-                        this.eventEmitter.emit(XMPPEvents.DISPLAY_NAME_CHANGED, from, displayName);
219
-                    }
220
-                    console.info("Display name: " + displayName, pres);
221
-                }
222
-                break;
223
-            case "userId":
224
-                member.id = node.value;
225
-                break;
226
-            case "email":
227
-                member.email = node.value;
228
-                break;
229
-            case "bridgeIsDown":
230
-                if(!this.bridgeIsDown) {
231
-                    this.bridgeIsDown = true;
232
-                    this.eventEmitter.emit(XMPPEvents.BRIDGE_DOWN);
233
-                }
234
-                break;
235
-            default :
236
-                this.processNode(node, from);
237
-        }
238
-
239
-    }
240
-
241
-    if (from == this.myroomjid) {
242
-        if (member.affiliation == 'owner')
243
-
244
-        if (this.role !== member.role) {
245
-            this.role = member.role;
246
-
247
-            this.eventEmitter.emit(XMPPEvents.LOCAL_ROLE_CHANGED,
248
-                member, this.isModerator());
249
-        }
250
-        if (!this.joined) {
251
-            this.joined = true;
252
-            this.eventEmitter.emit(XMPPEvents.MUC_JOINED, from, member);
253
-        }
254
-    } else if (this.members[from] === undefined) {
255
-        // new participant
256
-        this.members[from] = member;
257
-        console.log('entered', from, member);
258
-        if (member.isFocus) {
259
-            this.focusMucJid = from;
260
-            console.info("Ignore focus: " + from + ", real JID: " + member.jid);
261
-        }
262
-        else {
263
-            this.eventEmitter.emit(XMPPEvents.MUC_MEMBER_JOINED, from, member.id || member.email, member.nick);
264
-        }
265
-    } else {
266
-        // Presence update for existing participant
267
-        // Watch role change:
268
-        if (this.members[from].role != member.role) {
269
-            this.members[from].role = member.role;
270
-            this.eventEmitter.emit(XMPPEvents.MUC_ROLE_CHANGED,
271
-                member.role, member.nick);
272
-        }
273
-    }
274
-
275
-
276
-
277
-    if(!member.isFocus)
278
-        this.eventEmitter.emit(XMPPEvents.USER_ID_CHANGED, from, member.id || member.email);
279
-
280
-    // Trigger status message update
281
-    if (member.status) {
282
-        this.eventEmitter.emit(XMPPEvents.PRESENCE_STATUS, from, member);
283
-    }
284
-
285
-};
286
-
287
-ChatRoom.prototype.processNode = function (node, from) {
288
-    if(this.presHandlers[node.tagName])
289
-        this.presHandlers[node.tagName](node, from);
290
-};
291
-
292
-ChatRoom.prototype.sendMessage = function (body, nickname) {
293
-    var msg = $msg({to: this.roomjid, type: 'groupchat'});
294
-    msg.c('body', body).up();
295
-    if (nickname) {
296
-        msg.c('nick', {xmlns: 'http://jabber.org/protocol/nick'}).t(nickname).up().up();
297
-    }
298
-    this.connection.send(msg);
299
-    this.eventEmitter.emit(XMPPEvents.SENDING_CHAT_MESSAGE, body);
300
-};
301
-
302
-ChatRoom.prototype.setSubject = function (subject) {
303
-    var msg = $msg({to: this.roomjid, type: 'groupchat'});
304
-    msg.c('subject', subject);
305
-    this.connection.send(msg);
306
-    console.log("topic changed to " + subject);
307
-};
308
-
309
-
310
-ChatRoom.prototype.onParticipantLeft = function (jid) {
311
-
312
-    this.eventEmitter.emit(XMPPEvents.MUC_MEMBER_LEFT, jid);
313
-
314
-    this.moderator.onMucMemberLeft(jid);
315
-};
316
-
317
-ChatRoom.prototype.onPresenceUnavailable = function (pres, from) {
318
-    // room destroyed ?
319
-    if ($(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]' +
320
-        '>destroy').length) {
321
-        var reason;
322
-        var reasonSelect = $(pres).find(
323
-                '>x[xmlns="http://jabber.org/protocol/muc#user"]' +
324
-                '>destroy>reason');
325
-        if (reasonSelect.length) {
326
-            reason = reasonSelect.text();
327
-        }
328
-
329
-        this.xmpp.disposeConference(false);
330
-        this.eventEmitter.emit(XMPPEvents.MUC_DESTROYED, reason);
331
-        delete this.connection.emuc.rooms[Strophe.getBareJidFromJid(jid)];
332
-        return true;
333
-    }
334
-
335
-    // Status code 110 indicates that this notification is "self-presence".
336
-    if (!$(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="110"]').length) {
337
-        delete this.members[from];
338
-        this.onParticipantLeft(from);
339
-    }
340
-    // If the status code is 110 this means we're leaving and we would like
341
-    // to remove everyone else from our view, so we trigger the event.
342
-    else if (Object.keys(this.members).length > 1) {
343
-        for (var i in this.members) {
344
-            var member = this.members[i];
345
-            delete this.members[i];
346
-            this.onParticipantLeft(member);
347
-        }
348
-    }
349
-    if ($(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="307"]').length) {
350
-        if (this.myroomjid === from) {
351
-            this.xmpp.disposeConference(false);
352
-            this.eventEmitter.emit(XMPPEvents.KICKED);
353
-        }
354
-    }
355
-};
356
-
357
-ChatRoom.prototype.onMessage = function (msg, from) {
358
-    var nick =
359
-        $(msg).find('>nick[xmlns="http://jabber.org/protocol/nick"]')
360
-            .text() ||
361
-        Strophe.getResourceFromJid(from);
362
-
363
-    var txt = $(msg).find('>body').text();
364
-    var type = msg.getAttribute("type");
365
-    if (type == "error") {
366
-        this.eventEmitter.emit(XMPPEvents.CHAT_ERROR_RECEIVED,
367
-            $(msg).find('>text').text(), txt);
368
-        return true;
369
-    }
370
-
371
-    var subject = $(msg).find('>subject');
372
-    if (subject.length) {
373
-        var subjectText = subject.text();
374
-        if (subjectText || subjectText == "") {
375
-            this.eventEmitter.emit(XMPPEvents.SUBJECT_CHANGED, subjectText);
376
-            console.log("Subject is changed to " + subjectText);
377
-        }
378
-    }
379
-
380
-    // xep-0203 delay
381
-    var stamp = $(msg).find('>delay').attr('stamp');
382
-
383
-    if (!stamp) {
384
-        // or xep-0091 delay, UTC timestamp
385
-        stamp = $(msg).find('>[xmlns="jabber:x:delay"]').attr('stamp');
386
-
387
-        if (stamp) {
388
-            // the format is CCYYMMDDThh:mm:ss
389
-            var dateParts = stamp.match(/(\d{4})(\d{2})(\d{2}T\d{2}:\d{2}:\d{2})/);
390
-            stamp = dateParts[1] + "-" + dateParts[2] + "-" + dateParts[3] + "Z";
391
-        }
392
-    }
393
-
394
-    if (txt) {
395
-        console.log('chat', nick, txt);
396
-        this.eventEmitter.emit(XMPPEvents.MESSAGE_RECEIVED,
397
-            from, nick, txt, this.myroomjid, stamp);
398
-    }
399
-}
400
-
401
-ChatRoom.prototype.onPresenceError = function (pres, from) {
402
-    if ($(pres).find('>error[type="auth"]>not-authorized[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]').length) {
403
-        console.log('on password required', from);
404
-        this.eventEmitter.emit(XMPPEvents.PASSWORD_REQUIRED);
405
-    } else if ($(pres).find(
406
-        '>error[type="cancel"]>not-allowed[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]').length) {
407
-        var toDomain = Strophe.getDomainFromJid(pres.getAttribute('to'));
408
-        if (toDomain === this.xmpp.options.hosts.anonymousdomain) {
409
-            // enter the room by replying with 'not-authorized'. This would
410
-            // result in reconnection from authorized domain.
411
-            // We're either missing Jicofo/Prosody config for anonymous
412
-            // domains or something is wrong.
413
-            this.eventEmitter.emit(XMPPEvents.ROOM_JOIN_ERROR, pres);
414
-
415
-        } else {
416
-            console.warn('onPresError ', pres);
417
-            this.eventEmitter.emit(XMPPEvents.ROOM_CONNECT_ERROR, pres);
418
-        }
419
-    } else {
420
-        console.warn('onPresError ', pres);
421
-        this.eventEmitter.emit(XMPPEvents.ROOM_CONNECT_ERROR, pres);
422
-    }
423
-};
424
-
425
-ChatRoom.prototype.kick = function (jid) {
426
-    var kickIQ = $iq({to: this.roomjid, type: 'set'})
427
-        .c('query', {xmlns: 'http://jabber.org/protocol/muc#admin'})
428
-        .c('item', {nick: Strophe.getResourceFromJid(jid), role: 'none'})
429
-        .c('reason').t('You have been kicked.').up().up().up();
430
-
431
-    this.connection.sendIQ(
432
-        kickIQ,
433
-        function (result) {
434
-            console.log('Kick participant with jid: ', jid, result);
435
-        },
436
-        function (error) {
437
-            console.log('Kick participant error: ', error);
438
-        });
439
-};
440
-
441
-ChatRoom.prototype.lockRoom = function (key, onSuccess, onError, onNotSupported) {
442
-    //http://xmpp.org/extensions/xep-0045.html#roomconfig
443
-    var ob = this;
444
-    this.connection.sendIQ($iq({to: this.roomjid, type: 'get'}).c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'}),
445
-        function (res) {
446
-            if ($(res).find('>query>x[xmlns="jabber:x:data"]>field[var="muc#roomconfig_roomsecret"]').length) {
447
-                var formsubmit = $iq({to: ob.roomjid, type: 'set'}).c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'});
448
-                formsubmit.c('x', {xmlns: 'jabber:x:data', type: 'submit'});
449
-                formsubmit.c('field', {'var': 'FORM_TYPE'}).c('value').t('http://jabber.org/protocol/muc#roomconfig').up().up();
450
-                formsubmit.c('field', {'var': 'muc#roomconfig_roomsecret'}).c('value').t(key).up().up();
451
-                // Fixes a bug in prosody 0.9.+ https://code.google.com/p/lxmppd/issues/detail?id=373
452
-                formsubmit.c('field', {'var': 'muc#roomconfig_whois'}).c('value').t('anyone').up().up();
453
-                // FIXME: is muc#roomconfig_passwordprotectedroom required?
454
-                ob.connection.sendIQ(formsubmit,
455
-                    onSuccess,
456
-                    onError);
457
-            } else {
458
-                onNotSupported();
459
-            }
460
-        }, onError);
461
-};
462
-
463
-ChatRoom.prototype.addToPresence = function (key, values) {
464
-    values.tagName = key;
465
-    this.presMap["nodes"].push(values);
466
-};
467
-
468
-ChatRoom.prototype.removeFromPresence = function (key) {
469
-    for(var i = 0; i < this.presMap.nodes.length; i++)
470
-    {
471
-        if(key === this.presMap.nodes[i].tagName)
472
-            this.presMap.nodes.splice(i, 1);
473
-    }
474
-};
475
-
476
-ChatRoom.prototype.addPresenceListener = function (name, handler) {
477
-    this.presHandlers[name] = handler;
478
-}
479
-
480
-ChatRoom.prototype.removePresenceListener = function (name) {
481
-    delete this.presHandlers[name];
482
-}
483
-
484
-ChatRoom.prototype.isModerator = function (jid) {
485
-    return this.role === 'moderator';
486
-};
487
-
488
-ChatRoom.prototype.getMemberRole = function (peerJid) {
489
-    if (this.members[peerJid]) {
490
-        return this.members[peerJid].role;
491
-    }
492
-    return null;
493
-};
494
-
5
+var ChatRoom = require("./ChatRoom");
495 6
 
496 7
 module.exports = function(XMPP) {
497 8
     Strophe.addConnectionPlugin('emuc', {
@@ -505,13 +16,13 @@ module.exports = function(XMPP) {
505 16
             this.connection.addHandler(this.onPresenceError.bind(this), null, 'presence', 'error', null);
506 17
             this.connection.addHandler(this.onMessage.bind(this), null, 'message', null, null);
507 18
         },
508
-        createRoom: function (jid, password, eventEmitter) {
19
+        createRoom: function (jid, password, options) {
509 20
             var roomJid = Strophe.getBareJidFromJid(jid);
510 21
             if (this.rooms[roomJid]) {
511 22
                 console.error("You are already in the room!");
512 23
                 return;
513 24
             }
514
-            this.rooms[roomJid] = new ChatRoom(this.connection, jid, password, XMPP, eventEmitter);
25
+            this.rooms[roomJid] = new ChatRoom(this.connection, jid, password, XMPP, options);
515 26
             return this.rooms[roomJid];
516 27
         },
517 28
         doLeave: function (jid) {
@@ -566,6 +77,14 @@ module.exports = function(XMPP) {
566 77
 
567 78
             room.onMessage(msg, from);
568 79
             return true;
80
+        },
81
+
82
+        setJingleSession: function (from, session) {
83
+            var room = this.rooms[Strophe.getBareJidFromJid(from)];
84
+            if(!room)
85
+                return;
86
+
87
+            room.setJingleSession(session);
569 88
         }
570 89
     });
571 90
 };

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

@@ -104,10 +104,14 @@ module.exports = function(XMPP, eventEmitter) {
104 104
                         this.connection, XMPP, eventEmitter);
105 105
                     // configure session
106 106
 
107
+                    var fromBareJid = Strophe.getBareJidFromJid(fromJid);
108
+                    this.connection.emuc.setJingleSession(fromBareJid, sess);
109
+
107 110
                     sess.media_constraints = this.media_constraints;
108 111
                     sess.ice_config = this.ice_config;
109 112
 
110
-                    sess.initialize(Strophe.getBareJidFromJid(fromJid), false);
113
+                    sess.initialize(fromJid, false);
114
+                    eventEmitter.emit(XMPPEvents.CALL_INCOMING, sess);
111 115
                     // FIXME: setRemoteDescription should only be done when this call is to be accepted
112 116
                     sess.setOffer($(iq).find('>jingle'));
113 117
 
@@ -118,8 +122,6 @@ module.exports = function(XMPP, eventEmitter) {
118 122
                     // .sendAnswer and .accept
119 123
                     // or .sendTerminate -- not necessarily synchronous
120 124
 
121
-                    eventEmitter.emit(XMPPEvents.CALL_INCOMING, sess);
122
-
123 125
                     sess.sendAnswer();
124 126
                     sess.accept();
125 127
                     break;

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

@@ -1,5 +1,4 @@
1 1
 /* global $, APP, config, Strophe*/
2
-var Moderator = require("./moderator");
3 2
 var EventEmitter = require("events");
4 3
 var Pako = require("pako");
5 4
 var StreamEventTypes = require("../../service/RTC/StreamEventTypes");
@@ -165,7 +164,7 @@ XMPP.prototype.connect = function (jid, password) {
165 164
     return this._connect(jid, password);
166 165
 };
167 166
 
168
-XMPP.prototype.createRoom = function (roomName, useNicks, nick) {
167
+XMPP.prototype.createRoom = function (roomName, options, useNicks, nick) {
169 168
     var roomjid = roomName  + '@' + this.options.hosts.muc;
170 169
 
171 170
     if (useNicks) {
@@ -183,7 +182,7 @@ XMPP.prototype.createRoom = function (roomName, useNicks, nick) {
183 182
         roomjid += '/' + tmpJid;
184 183
     }
185 184
 
186
-    return this.connection.emuc.createRoom(roomjid, null, this.eventEmitter);
185
+    return this.connection.emuc.createRoom(roomjid, null, options);
187 186
 }
188 187
 
189 188
 XMPP.prototype.addListener = function(type, listener) {
@@ -194,6 +193,7 @@ XMPP.prototype.removeListener = function (type, listener) {
194 193
     this.eventEmitter.removeListener(type, listener);
195 194
 };
196 195
 
196
+//FIXME: this should work with the room
197 197
 XMPP.prototype.leaveRoom = function (jid) {
198 198
     var handler = this.connection.jingle.jid2session[jid];
199 199
     if (handler && handler.peerconnection) {
@@ -213,80 +213,6 @@ XMPP.prototype.leaveRoom = function (jid) {
213 213
     this.connection.emuc.doLeave(jid);
214 214
 };
215 215
 
216
-XMPP.prototype.isConferenceInProgress = function () {
217
-    return this.connection && this.connection.jingle.activecall &&
218
-        this.connection.jingle.activecall.peerconnection;
219
-};
220
-
221
-XMPP.prototype.switchStreams = function (stream, oldStream, callback, isAudio) {
222
-    if (this.isConferenceInProgress()) {
223
-        // FIXME: will block switchInProgress on true value in case of exception
224
-        this.connection.jingle.activecall.switchStreams(stream, oldStream, callback, isAudio);
225
-    } else {
226
-        // We are done immediately
227
-        console.warn("No conference handler or conference not started yet");
228
-        callback();
229
-    }
230
-};
231
-
232
-XMPP.prototype.sendVideoInfoPresence = function (mute) {
233
-    if(!this.connection)
234
-        return;
235
-    this.connection.emuc.addVideoInfoToPresence(mute);
236
-    this.connection.emuc.sendPresence();
237
-};
238
-
239
-XMPP.prototype.setVideoMute = function (mute, callback, options) {
240
-    if(!this.connection)
241
-        return;
242
-    var self = this;
243
-    var localCallback = function (mute) {
244
-        self.sendVideoInfoPresence(mute);
245
-        return callback(mute);
246
-    };
247
-
248
-    if(this.connection.jingle.activecall)
249
-    {
250
-        this.connection.jingle.activecall.setVideoMute(
251
-            mute, localCallback, options);
252
-    }
253
-    else {
254
-        localCallback(mute);
255
-    }
256
-
257
-};
258
-
259
-XMPP.prototype.setAudioMute = function (mute, callback) {
260
-    if (!(this.connection && RTC.localAudio)) {
261
-        return false;
262
-    }
263
-
264
-    if (this.forceMuted && !mute) {
265
-        console.info("Asking focus for unmute");
266
-        this.connection.moderate.setMute(this.connection.emuc.myroomjid, mute);
267
-        // FIXME: wait for result before resetting muted status
268
-        this.forceMuted = false;
269
-    }
270
-
271
-    if (mute == RTC.localAudio.isMuted()) {
272
-        // Nothing to do
273
-        return true;
274
-    }
275
-
276
-    RTC.localAudio.setMute(mute);
277
-    this.sendAudioInfoPresence(mute, callback);
278
-    return true;
279
-};
280
-
281
-XMPP.prototype.sendAudioInfoPresence = function(mute, callback) {
282
-    if(this.connection) {
283
-        this.connection.emuc.addAudioInfoToPresence(mute);
284
-        this.connection.emuc.sendPresence();
285
-    }
286
-    callback();
287
-    return true;
288
-};
289
-
290 216
 /**
291 217
  * Sends 'data' as a log message to the focus. Returns true iff a message
292 218
  * was sent.
@@ -351,11 +277,6 @@ XMPP.prototype.getSessions = function () {
351 277
     return this.connection.jingle.sessions;
352 278
 };
353 279
 
354
-XMPP.prototype.removeStream = function (stream) {
355
-    if (!this.isConferenceInProgress())
356
-        return;
357
-    this.connection.jingle.activecall.peerconnection.removeStream(stream);
358
-};
359 280
 
360 281
 XMPP.prototype.disconnect = function () {
361 282
     if (this.disconnectInProgress || !this.connection || !this.connection.connected)

+ 1
- 1
package.json 查看文件

@@ -20,7 +20,7 @@
20 20
       "pako": "*",
21 21
       "i18next-client": "1.7.7",
22 22
       "sdp-interop": "0.1.4",
23
-      "sdp-transform": "1.4.0",
23
+      "sdp-transform": "1.4.1",
24 24
       "sdp-simulcast": "0.1.0",
25 25
       "async": "0.9.0",
26 26
       "retry": "0.6.1",

+ 2
- 1
service/xmpp/XMPPEvents.js 查看文件

@@ -50,6 +50,7 @@ var XMPPEvents = {
50 50
     ROOM_CONNECT_ERROR: 'xmpp.room_connect_error',
51 51
     // xmpp is connected and obtained user media
52 52
     READY_TO_JOIN: 'xmpp.ready_to_join',
53
-    FOCUS_LEFT: "xmpp.focus_left"
53
+    FOCUS_LEFT: "xmpp.focus_left",
54
+    REMOTE_STREAM_RECEIVED: "xmpp.remote_stream_received"
54 55
 };
55 56
 module.exports = XMPPEvents;

Loading…
取消
儲存