ソースを参照

removed xmpp and RTC modules

master
isymchych 9年前
コミット
c36001f861

+ 0
- 211
modules/RTC/DataChannels.js ファイルの表示

@@ -1,211 +0,0 @@
1
-/* global config, APP, Strophe */
2
-/* jshint -W101 */
3
-
4
-// cache datachannels to avoid garbage collection
5
-// https://code.google.com/p/chromium/issues/detail?id=405545
6
-var RTCEvents = require("../../service/RTC/RTCEvents");
7
-
8
-var _dataChannels = [];
9
-var eventEmitter = null;
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
-
53
-                if ("DominantSpeakerEndpointChangeEvent" === colibriClass) {
54
-                    // Endpoint ID from the Videobridge.
55
-                    var dominantSpeakerEndpoint = obj.dominantSpeakerEndpoint;
56
-
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 = Boolean(oldValue).valueOf();
73
-                        }
74
-                    }
75
-                    if ((type = typeof newValue) !== 'boolean') {
76
-                        if (type === 'string') {
77
-                            newValue = (newValue == "true");
78
-                        } else {
79
-                            newValue = Boolean(newValue).valueOf();
80
-                        }
81
-                    }
82
-
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.info(
94
-                        "Data channel new last-n event: ",
95
-                        lastNEndpoints, endpointsEnteringLastN, obj);
96
-                    eventEmitter.emit(RTCEvents.LASTN_ENDPOINT_CHANGED,
97
-                        lastNEndpoints, endpointsEnteringLastN, obj);
98
-                }
99
-                else {
100
-                    console.debug("Data channel JSON-formatted message: ", obj);
101
-                    // The received message appears to be appropriately
102
-                    // formatted (i.e. is a JSON object which assigns a value to
103
-                    // the mandatory property colibriClass) so don't just
104
-                    // swallow it, expose it to public consumption.
105
-                    eventEmitter.emit("rtc.datachannel." + colibriClass, obj);
106
-                }
107
-            }
108
-        };
109
-
110
-        dataChannel.onclose = function () {
111
-            console.info("The Data Channel closed", dataChannel);
112
-            var idx = _dataChannels.indexOf(dataChannel);
113
-            if (idx > -1)
114
-                _dataChannels = _dataChannels.splice(idx, 1);
115
-        };
116
-        _dataChannels.push(dataChannel);
117
-    },
118
-
119
-    /**
120
-     * Binds "ondatachannel" event listener to given PeerConnection instance.
121
-     * @param peerConnection WebRTC peer connection instance.
122
-     */
123
-    init: function (peerConnection, emitter) {
124
-        if(!config.openSctp)
125
-            return;
126
-
127
-        peerConnection.ondatachannel = this.onDataChannel;
128
-        eventEmitter = emitter;
129
-
130
-        // Sample code for opening new data channel from Jitsi Meet to the bridge.
131
-        // Although it's not a requirement to open separate channels from both bridge
132
-        // and peer as single channel can be used for sending and receiving data.
133
-        // So either channel opened by the bridge or the one opened here is enough
134
-        // for communication with the bridge.
135
-        /*var dataChannelOptions =
136
-         {
137
-         reliable: true
138
-         };
139
-         var dataChannel
140
-         = peerConnection.createDataChannel("myChannel", dataChannelOptions);
141
-
142
-         // Can be used only when is in open state
143
-         dataChannel.onopen = function ()
144
-         {
145
-         dataChannel.send("My channel !!!");
146
-         };
147
-         dataChannel.onmessage = function (event)
148
-         {
149
-         var msgData = event.data;
150
-         console.info("Got My Data Channel Message:", msgData, dataChannel);
151
-         };*/
152
-    },
153
-
154
-    handleSelectedEndpointEvent: function (userResource) {
155
-        onXXXEndpointChanged("selected", userResource);
156
-    },
157
-    handlePinnedEndpointEvent: function (userResource) {
158
-        onXXXEndpointChanged("pinned", userResource);
159
-    },
160
-
161
-    some: function (callback, thisArg) {
162
-        if (_dataChannels && _dataChannels.length !== 0) {
163
-            if (thisArg)
164
-                return _dataChannels.some(callback, thisArg);
165
-            else
166
-                return _dataChannels.some(callback);
167
-        } else {
168
-            return false;
169
-        }
170
-    }
171
-};
172
-
173
-/**
174
- * Notifies Videobridge about a change in the value of a specific
175
- * endpoint-related property such as selected endpoint and pinnned endpoint.
176
- *
177
- * @param xxx the name of the endpoint-related property whose value changed
178
- * @param userResource the new value of the endpoint-related property after the
179
- * change
180
- */
181
-function onXXXEndpointChanged(xxx, userResource) {
182
-    // Derive the correct words from xxx such as selected and Selected, pinned
183
-    // and Pinned.
184
-    var head = xxx.charAt(0);
185
-    var tail = xxx.substring(1);
186
-    var lower = head.toLowerCase() + tail;
187
-    var upper = head.toUpperCase() + tail;
188
-
189
-    // Notify Videobridge about the specified endpoint change.
190
-    console.log(lower + ' endpoint changed: ', userResource);
191
-    DataChannels.some(function (dataChannel) {
192
-        if (dataChannel.readyState == 'open') {
193
-            console.log(
194
-                    'sending ' + lower
195
-                        + ' endpoint changed notification to the bridge: ',
196
-                    userResource);
197
-
198
-            var jsonObject = {};
199
-
200
-            jsonObject.colibriClass = (upper + 'EndpointChangedEvent');
201
-            jsonObject[lower + "Endpoint"]
202
-                = (userResource ? userResource : null);
203
-            dataChannel.send(JSON.stringify(jsonObject));
204
-
205
-            return true;
206
-        }
207
-    });
208
-}
209
-
210
-module.exports = DataChannels;
211
-

+ 0
- 145
modules/RTC/LocalStream.js ファイルの表示

@@ -1,145 +0,0 @@
1
-/* global APP */
2
-var MediaStreamType = require("../../service/RTC/MediaStreamTypes");
3
-var RTCEvents = require("../../service/RTC/RTCEvents");
4
-var RTCBrowserType = require("./RTCBrowserType");
5
-var StreamEventTypes = require("../../service/RTC/StreamEventTypes.js");
6
-
7
-/**
8
- * This implements 'onended' callback normally fired by WebRTC after the stream
9
- * is stopped. There is no such behaviour yet in FF, so we have to add it.
10
- * @param stream original WebRTC stream object to which 'onended' handling
11
- *               will be added.
12
- */
13
-function implementOnEndedHandling(localStream) {
14
-    var stream = localStream.getOriginalStream();
15
-    var originalStop = stream.stop;
16
-    stream.stop = function () {
17
-        originalStop.apply(stream);
18
-        if (localStream.isActive()) {
19
-            stream.onended();
20
-        }
21
-    };
22
-}
23
-
24
-function LocalStream(stream, type, eventEmitter, videoType, isGUMStream) {
25
-    this.stream = stream;
26
-    this.eventEmitter = eventEmitter;
27
-    this.type = type;
28
-    this.videoType = videoType;
29
-    this.isGUMStream = true;
30
-    if(isGUMStream === false)
31
-        this.isGUMStream = isGUMStream;
32
-    var self = this;
33
-    if (MediaStreamType.AUDIO_TYPE === type) {
34
-        this.getTracks = function () {
35
-            return self.stream.getAudioTracks();
36
-        };
37
-    } else {
38
-        this.getTracks = function () {
39
-            return self.stream.getVideoTracks();
40
-        };
41
-    }
42
-
43
-    APP.RTC.addMediaStreamInactiveHandler(
44
-        this.stream,
45
-        function () {
46
-            self.streamEnded();
47
-        });
48
-
49
-    if (RTCBrowserType.isFirefox()) {
50
-        implementOnEndedHandling(this);
51
-    }
52
-}
53
-
54
-LocalStream.prototype.streamEnded = function () {
55
-    this.eventEmitter.emit(StreamEventTypes.EVENT_TYPE_LOCAL_ENDED, this);
56
-};
57
-
58
-LocalStream.prototype.getOriginalStream = function()
59
-{
60
-    return this.stream;
61
-};
62
-
63
-LocalStream.prototype.isAudioStream = function () {
64
-    return MediaStreamType.AUDIO_TYPE === this.type;
65
-};
66
-
67
-LocalStream.prototype.isVideoStream = function () {
68
-    return MediaStreamType.VIDEO_TYPE === this.type;
69
-};
70
-
71
-LocalStream.prototype.setMute = function (mute)
72
-{
73
-    var isAudio = this.isAudioStream();
74
-    var eventType = isAudio ? RTCEvents.AUDIO_MUTE : RTCEvents.VIDEO_MUTE;
75
-
76
-    if ((window.location.protocol != "https:" && this.isGUMStream) ||
77
-        (isAudio && this.isGUMStream) || this.videoType === "screen" ||
78
-        // FIXME FF does not support 'removeStream' method used to mute
79
-        RTCBrowserType.isFirefox()) {
80
-
81
-        var tracks = this.getTracks();
82
-        for (var idx = 0; idx < tracks.length; idx++) {
83
-            tracks[idx].enabled = !mute;
84
-        }
85
-        this.eventEmitter.emit(eventType, mute);
86
-    } else {
87
-        if (mute) {
88
-            APP.xmpp.removeStream(this.stream);
89
-            APP.RTC.stopMediaStream(this.stream);
90
-            this.eventEmitter.emit(eventType, true);
91
-        } else {
92
-            var self = this;
93
-            APP.RTC.rtcUtils.obtainAudioAndVideoPermissions(
94
-                (this.isAudioStream() ? ["audio"] : ["video"]),
95
-                function (stream) {
96
-                    if (isAudio) {
97
-                        APP.RTC.changeLocalAudio(stream,
98
-                            function () {
99
-                                self.eventEmitter.emit(eventType, false);
100
-                            });
101
-                    } else {
102
-                        APP.RTC.changeLocalVideo(stream, false,
103
-                            function () {
104
-                                self.eventEmitter.emit(eventType, false);
105
-                            });
106
-                    }
107
-                });
108
-        }
109
-    }
110
-};
111
-
112
-LocalStream.prototype.isMuted = function () {
113
-    var tracks = [];
114
-    if (this.isAudioStream()) {
115
-        tracks = this.stream.getAudioTracks();
116
-    } else {
117
-        if (!this.isActive())
118
-            return true;
119
-        tracks = this.stream.getVideoTracks();
120
-    }
121
-    for (var idx = 0; idx < tracks.length; idx++) {
122
-        if(tracks[idx].enabled)
123
-            return false;
124
-    }
125
-    return true;
126
-};
127
-
128
-LocalStream.prototype.getId = function () {
129
-    return this.stream.getTracks()[0].id;
130
-};
131
-
132
-/**
133
- * Checks whether the MediaStream is avtive/not ended.
134
- * When there is no check for active we don't have information and so
135
- * will return that stream is active (in case of FF).
136
- * @returns {boolean} whether MediaStream is active.
137
- */
138
-LocalStream.prototype.isActive = function () {
139
-    if((typeof this.stream.active !== "undefined"))
140
-        return this.stream.active;
141
-    else
142
-        return true;
143
-};
144
-
145
-module.exports = LocalStream;

+ 0
- 57
modules/RTC/MediaStream.js ファイルの表示

@@ -1,57 +0,0 @@
1
-var MediaStreamType = require("../../service/RTC/MediaStreamTypes");
2
-
3
-/**
4
- * Creates a MediaStream object for the given data, session id and ssrc.
5
- * It is a wrapper class for the MediaStream.
6
- *
7
- * @param data the data object from which we obtain the stream,
8
- * the peerjid, etc.
9
- * @param ssrc the ssrc corresponding to this MediaStream
10
- * @param mute the whether this MediaStream is muted
11
- *
12
- * @constructor
13
- */
14
-function MediaStream(data, ssrc, browser, eventEmitter, muted, type) {
15
-
16
-    // XXX(gp) to minimize headaches in the future, we should build our
17
-    // abstractions around tracks and not streams. ORTC is track based API.
18
-    // Mozilla expects m-lines to represent media tracks.
19
-    //
20
-    // Practically, what I'm saying is that we should have a MediaTrack class
21
-    // and not a MediaStream class.
22
-    //
23
-    // Also, we should be able to associate multiple SSRCs with a MediaTrack as
24
-    // a track might have an associated RTX and FEC sources.
25
-
26
-    if (!type) {
27
-        console.log("Errrm...some code needs an update...");
28
-    }
29
-
30
-    this.stream = data.stream;
31
-    this.peerjid = data.peerjid;
32
-    this.videoType = data.videoType;
33
-    this.ssrc = ssrc;
34
-    this.type = type;
35
-    this.muted = muted;
36
-    this.eventEmitter = eventEmitter;
37
-}
38
-
39
-// FIXME duplicated with LocalStream methods - extract base class
40
-MediaStream.prototype.isAudioStream = function () {
41
-    return MediaStreamType.AUDIO_TYPE === this.type;
42
-};
43
-
44
-MediaStream.prototype.isVideoStream = function () {
45
-    return MediaStreamType.VIDEO_TYPE === this.type;
46
-};
47
-
48
-MediaStream.prototype.getOriginalStream = function () {
49
-    return this.stream;
50
-};
51
-
52
-MediaStream.prototype.setMute = function (value) {
53
-    this.stream.muted = value;
54
-    this.muted = value;
55
-};
56
-
57
-module.exports = MediaStream;

+ 0
- 335
modules/RTC/RTC.js ファイルの表示

@@ -1,335 +0,0 @@
1
-/* global APP */
2
-var EventEmitter = require("events");
3
-var RTCBrowserType = require("./RTCBrowserType");
4
-var RTCUtils = require("./RTCUtils.js");
5
-var LocalStream = require("./LocalStream.js");
6
-var DataChannels = require("./DataChannels");
7
-var MediaStream = require("./MediaStream.js");
8
-var DesktopSharingEventTypes
9
-    = require("../../service/desktopsharing/DesktopSharingEventTypes");
10
-var MediaStreamType = require("../../service/RTC/MediaStreamTypes");
11
-var StreamEventTypes = require("../../service/RTC/StreamEventTypes.js");
12
-var RTCEvents = require("../../service/RTC/RTCEvents.js");
13
-var XMPPEvents = require("../../service/xmpp/XMPPEvents");
14
-var UIEvents = require("../../service/UI/UIEvents");
15
-
16
-var eventEmitter = new EventEmitter();
17
-
18
-
19
-function getMediaStreamUsage()
20
-{
21
-    var result = {
22
-        audio: true,
23
-        video: true
24
-    };
25
-
26
-    /** There are some issues with the desktop sharing
27
-     * when this property is enabled.
28
-     * WARNING: We must change the implementation to start video/audio if we
29
-     * receive from the focus that the peer is not muted.
30
-
31
-     var isSecureConnection = window.location.protocol == "https:";
32
-
33
-    if(config.disableEarlyMediaPermissionRequests || !isSecureConnection)
34
-    {
35
-        result = {
36
-            audio: false,
37
-            video: false
38
-        };
39
-
40
-    }
41
-    **/
42
-
43
-    return result;
44
-}
45
-
46
-var RTC = {
47
-    // Exposes DataChannels to public consumption (e.g. jitsi-meet-torture)
48
-    // without the necessity to require the module.
49
-    "DataChannels": DataChannels,
50
-
51
-    rtcUtils: null,
52
-    devices: {
53
-        audio: true,
54
-        video: true
55
-    },
56
-    remoteStreams: {},
57
-    localAudio: null,
58
-    localVideo: null,
59
-    addStreamListener: function (listener, eventType) {
60
-        eventEmitter.on(eventType, listener);
61
-    },
62
-    addListener: function (type, listener) {
63
-        eventEmitter.on(type, listener);
64
-    },
65
-    removeStreamListener: function (listener, eventType) {
66
-        if(!(eventType instanceof StreamEventTypes))
67
-            throw "Illegal argument";
68
-
69
-        eventEmitter.removeListener(eventType, listener);
70
-    },
71
-    createLocalStream: function (stream, type, change, videoType,
72
-                                 isMuted, isGUMStream) {
73
-
74
-        var localStream =
75
-            new LocalStream(stream, type, eventEmitter, videoType, isGUMStream);
76
-        if(isMuted === true)
77
-            localStream.setMute(true);
78
-
79
-        if (MediaStreamType.AUDIO_TYPE === type) {
80
-            this.localAudio = localStream;
81
-        } else {
82
-            this.localVideo = localStream;
83
-        }
84
-        var eventType = StreamEventTypes.EVENT_TYPE_LOCAL_CREATED;
85
-        if(change)
86
-            eventType = StreamEventTypes.EVENT_TYPE_LOCAL_CHANGED;
87
-
88
-        eventEmitter.emit(eventType, localStream, isMuted);
89
-        return localStream;
90
-    },
91
-    createRemoteStream: function (data, ssrc) {
92
-        var jid = data.peerjid || APP.xmpp.myJid();
93
-
94
-        // check the video muted state from last stored presence if any
95
-        var muted = false;
96
-        var pres = APP.xmpp.getLastPresence(jid);
97
-        if (pres && pres.videoMuted) {
98
-            muted = pres.videoMuted;
99
-        }
100
-
101
-        var self = this;
102
-        [MediaStreamType.AUDIO_TYPE, MediaStreamType.VIDEO_TYPE].forEach(
103
-            function (type) {
104
-            var tracks =
105
-                type == MediaStreamType.AUDIO_TYPE
106
-                ? data.stream.getAudioTracks() : data.stream.getVideoTracks();
107
-            if (!tracks || !Array.isArray(tracks) || !tracks.length) {
108
-                console.log("Not creating a(n) " + type + " stream: no tracks");
109
-                return;
110
-            }
111
-
112
-            var remoteStream = new MediaStream(data, ssrc,
113
-                RTCBrowserType.getBrowserType(), eventEmitter, muted, type);
114
-
115
-            if (!self.remoteStreams[jid]) {
116
-                self.remoteStreams[jid] = {};
117
-            }
118
-            self.remoteStreams[jid][type] = remoteStream;
119
-            eventEmitter.emit(StreamEventTypes.EVENT_TYPE_REMOTE_CREATED,
120
-                remoteStream);
121
-        });
122
-    },
123
-    getPCConstraints: function () {
124
-        return this.rtcUtils.pc_constraints;
125
-    },
126
-    getUserMediaWithConstraints:function(um, success_callback,
127
-                                         failure_callback, resolution,
128
-                                         bandwidth, fps, desktopStream)
129
-    {
130
-        return this.rtcUtils.getUserMediaWithConstraints(um, success_callback,
131
-            failure_callback, resolution, bandwidth, fps, desktopStream);
132
-    },
133
-    attachMediaStream:  function (elSelector, stream) {
134
-        this.rtcUtils.attachMediaStream(elSelector, stream);
135
-    },
136
-    getStreamID:  function (stream) {
137
-        return this.rtcUtils.getStreamID(stream);
138
-    },
139
-    getVideoSrc: function (element) {
140
-        return this.rtcUtils.getVideoSrc(element);
141
-    },
142
-    setVideoSrc: function (element, src) {
143
-        this.rtcUtils.setVideoSrc(element, src);
144
-    },
145
-    getVideoElementName: function () {
146
-        return RTCBrowserType.isTemasysPluginUsed() ? 'object' : 'video';
147
-    },
148
-    dispose: function() {
149
-        if (this.rtcUtils) {
150
-            this.rtcUtils = null;
151
-        }
152
-    },
153
-    stop:  function () {
154
-        this.dispose();
155
-    },
156
-    start: function () {
157
-        var self = this;
158
-        APP.desktopsharing.addListener(
159
-            DesktopSharingEventTypes.NEW_STREAM_CREATED,
160
-            function (stream, isUsingScreenStream, callback) {
161
-                self.changeLocalVideo(stream, isUsingScreenStream, callback);
162
-        });
163
-        APP.xmpp.addListener(XMPPEvents.CALL_INCOMING, function(event) {
164
-            DataChannels.init(event.peerconnection, eventEmitter);
165
-        });
166
-        APP.UI.addListener(UIEvents.SELECTED_ENDPOINT,
167
-            DataChannels.handleSelectedEndpointEvent);
168
-        APP.UI.addListener(UIEvents.PINNED_ENDPOINT,
169
-            DataChannels.handlePinnedEndpointEvent);
170
-
171
-        // In case of IE we continue from 'onReady' callback
172
-        // passed to RTCUtils constructor. It will be invoked by Temasys plugin
173
-        // once it is initialized.
174
-        var onReady = function () {
175
-            eventEmitter.emit(RTCEvents.RTC_READY, true);
176
-            self.rtcUtils.obtainAudioAndVideoPermissions(
177
-                null, null, getMediaStreamUsage());
178
-        };
179
-
180
-        this.rtcUtils = new RTCUtils(this, eventEmitter, onReady);
181
-
182
-        // Call onReady() if Temasys plugin is not used
183
-        if (!RTCBrowserType.isTemasysPluginUsed()) {
184
-            onReady();
185
-        }
186
-    },
187
-    muteRemoteVideoStream: function (jid, value) {
188
-        var stream;
189
-
190
-        if(this.remoteStreams[jid] &&
191
-            this.remoteStreams[jid][MediaStreamType.VIDEO_TYPE]) {
192
-            stream = this.remoteStreams[jid][MediaStreamType.VIDEO_TYPE];
193
-        }
194
-
195
-        if(!stream)
196
-            return true;
197
-
198
-        if (value != stream.muted) {
199
-            stream.setMute(value);
200
-            return true;
201
-        }
202
-        return false;
203
-    },
204
-    changeLocalVideo: function (stream, isUsingScreenStream, callback) {
205
-        var oldStream = this.localVideo.getOriginalStream();
206
-        var type = (isUsingScreenStream ? "screen" : "camera");
207
-        var localCallback = callback;
208
-        if(this.localVideo.isMuted() && this.localVideo.videoType !== type) {
209
-            localCallback = function() {
210
-                APP.xmpp.setVideoMute(false, function(mute) {
211
-                    eventEmitter.emit(RTCEvents.VIDEO_MUTE, mute);
212
-                });
213
-
214
-                callback();
215
-            };
216
-        }
217
-        // FIXME: Workaround for FF/IE/Safari
218
-        if (stream && stream.videoStream) {
219
-            stream = stream.videoStream;
220
-        }
221
-        var videoStream = this.rtcUtils.createStream(stream, true);
222
-        this.localVideo =
223
-            this.createLocalStream(videoStream, "video", true, type);
224
-        // Stop the stream
225
-        this.stopMediaStream(oldStream);
226
-
227
-        APP.xmpp.switchStreams(videoStream, oldStream,localCallback);
228
-    },
229
-    changeLocalAudio: function (stream, callback) {
230
-        var oldStream = this.localAudio.getOriginalStream();
231
-        var newStream = this.rtcUtils.createStream(stream);
232
-        this.localAudio
233
-            = this.createLocalStream(
234
-                    newStream, MediaStreamType.AUDIO_TYPE, true);
235
-        // Stop the stream
236
-        this.stopMediaStream(oldStream);
237
-        APP.xmpp.switchStreams(newStream, oldStream, callback, true);
238
-    },
239
-    isVideoMuted: function (jid) {
240
-        if (jid === APP.xmpp.myJid()) {
241
-            var localVideo = APP.RTC.localVideo;
242
-            return (!localVideo || localVideo.isMuted());
243
-        } else {
244
-            if (!APP.RTC.remoteStreams[jid] ||
245
-                !APP.RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE]) {
246
-                return null;
247
-            }
248
-            return APP.RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE].muted;
249
-        }
250
-    },
251
-    setVideoMute: function (mute, callback, options) {
252
-        if (!this.localVideo)
253
-            return;
254
-
255
-        if (mute == APP.RTC.localVideo.isMuted())
256
-        {
257
-            APP.xmpp.sendVideoInfoPresence(mute);
258
-            if (callback)
259
-                callback(mute);
260
-        }
261
-        else
262
-        {
263
-            APP.RTC.localVideo.setMute(mute);
264
-            APP.xmpp.setVideoMute(
265
-                mute,
266
-                callback,
267
-                options);
268
-        }
269
-    },
270
-    setDeviceAvailability: function (devices) {
271
-        if(!devices)
272
-            return;
273
-        if(devices.audio === true || devices.audio === false)
274
-            this.devices.audio = devices.audio;
275
-        if(devices.video === true || devices.video === false)
276
-            this.devices.video = devices.video;
277
-        eventEmitter.emit(RTCEvents.AVAILABLE_DEVICES_CHANGED, this.devices);
278
-    },
279
-    /**
280
-     * A method to handle stopping of the stream.
281
-     * One point to handle the differences in various implementations.
282
-     * @param mediaStream MediaStream object to stop.
283
-     */
284
-    stopMediaStream: function (mediaStream) {
285
-        mediaStream.getTracks().forEach(function (track) {
286
-            // stop() not supported with IE
287
-            if (track.stop) {
288
-                track.stop();
289
-            }
290
-        });
291
-
292
-        // leave stop for implementation still using it
293
-        if (mediaStream.stop) {
294
-            mediaStream.stop();
295
-        }
296
-    },
297
-    /**
298
-     * Adds onended/inactive handler to a MediaStream.
299
-     * @param mediaStream a MediaStream to attach onended/inactive handler
300
-     * @param handler the handler
301
-     */
302
-    addMediaStreamInactiveHandler: function (mediaStream, handler) {
303
-        if (mediaStream.addEventListener) {
304
-            // chrome
305
-            if(typeof mediaStream.active !== "undefined")
306
-                mediaStream.oninactive = handler;
307
-            else
308
-                mediaStream.onended = handler;
309
-        } else {
310
-            // themasys
311
-            mediaStream.attachEvent('ended', function () {
312
-                handler(mediaStream);
313
-            });
314
-        }
315
-    },
316
-    /**
317
-     * Removes onended/inactive handler.
318
-     * @param mediaStream the MediaStream to remove the handler from.
319
-     * @param handler the handler to remove.
320
-     */
321
-    removeMediaStreamInactiveHandler: function (mediaStream, handler) {
322
-        if (mediaStream.removeEventListener) {
323
-            // chrome
324
-            if(typeof mediaStream.active !== "undefined")
325
-                mediaStream.oninactive = null;
326
-            else
327
-                mediaStream.onended = null;
328
-        } else {
329
-            // themasys
330
-            mediaStream.detachEvent('ended', handler);
331
-        }
332
-    }
333
-};
334
-
335
-module.exports = RTC;

+ 0
- 574
modules/RTC/RTCUtils.js ファイルの表示

@@ -1,574 +0,0 @@
1
-/* global APP, config, require, attachMediaStream, getUserMedia,
2
-    RTCPeerConnection, webkitMediaStream, webkitURL, webkitRTCPeerConnection,
3
-    mozRTCIceCandidate, mozRTCSessionDescription, mozRTCPeerConnection */
4
-/* jshint -W101 */
5
-var MediaStreamType = require("../../service/RTC/MediaStreamTypes");
6
-var RTCBrowserType = require("./RTCBrowserType");
7
-var Resolutions = require("../../service/RTC/Resolutions");
8
-var RTCEvents = require("../../service/RTC/RTCEvents");
9
-var AdapterJS = require("./adapter.screenshare");
10
-
11
-var currentResolution = null;
12
-
13
-function getPreviousResolution(resolution) {
14
-    if(!Resolutions[resolution])
15
-        return null;
16
-    var order = Resolutions[resolution].order;
17
-    var res = null;
18
-    var resName = null;
19
-    for(var i in Resolutions) {
20
-        var tmp = Resolutions[i];
21
-        if (!res || (res.order < tmp.order && tmp.order < order)) {
22
-            resName = i;
23
-            res = tmp;
24
-        }
25
-    }
26
-    return resName;
27
-}
28
-
29
-function setResolutionConstraints(constraints, resolution) {
30
-    var isAndroid = RTCBrowserType.isAndroid();
31
-
32
-    if (Resolutions[resolution]) {
33
-        constraints.video.mandatory.minWidth = Resolutions[resolution].width;
34
-        constraints.video.mandatory.minHeight = Resolutions[resolution].height;
35
-    }
36
-    else if (isAndroid) {
37
-        // FIXME can't remember if the purpose of this was to always request
38
-        //       low resolution on Android ? if yes it should be moved up front
39
-        constraints.video.mandatory.minWidth = 320;
40
-        constraints.video.mandatory.minHeight = 240;
41
-        constraints.video.mandatory.maxFrameRate = 15;
42
-    }
43
-
44
-    if (constraints.video.mandatory.minWidth)
45
-        constraints.video.mandatory.maxWidth =
46
-            constraints.video.mandatory.minWidth;
47
-    if (constraints.video.mandatory.minHeight)
48
-        constraints.video.mandatory.maxHeight =
49
-            constraints.video.mandatory.minHeight;
50
-}
51
-
52
-function getConstraints(um, resolution, bandwidth, fps, desktopStream) {
53
-    var constraints = {audio: false, video: false};
54
-
55
-    if (um.indexOf('video') >= 0) {
56
-        // same behaviour as true
57
-        constraints.video = { mandatory: {}, optional: [] };
58
-
59
-        constraints.video.optional.push({ googLeakyBucket: true });
60
-
61
-        setResolutionConstraints(constraints, resolution);
62
-    }
63
-    if (um.indexOf('audio') >= 0) {
64
-        if (!RTCBrowserType.isFirefox()) {
65
-            // same behaviour as true
66
-            constraints.audio = { mandatory: {}, optional: []};
67
-            // if it is good enough for hangouts...
68
-            constraints.audio.optional.push(
69
-                {googEchoCancellation: true},
70
-                {googAutoGainControl: true},
71
-                {googNoiseSupression: true},
72
-                {googHighpassFilter: true},
73
-                {googNoisesuppression2: true},
74
-                {googEchoCancellation2: true},
75
-                {googAutoGainControl2: true}
76
-            );
77
-        } else {
78
-            constraints.audio = true;
79
-        }
80
-    }
81
-    if (um.indexOf('screen') >= 0) {
82
-        if (RTCBrowserType.isChrome()) {
83
-            constraints.video = {
84
-                mandatory: {
85
-                    chromeMediaSource: "screen",
86
-                    googLeakyBucket: true,
87
-                    maxWidth: window.screen.width,
88
-                    maxHeight: window.screen.height,
89
-                    maxFrameRate: 3
90
-                },
91
-                optional: []
92
-            };
93
-        } else if (RTCBrowserType.isTemasysPluginUsed()) {
94
-            constraints.video = {
95
-                optional: [
96
-                    {
97
-                        sourceId: AdapterJS.WebRTCPlugin.plugin.screensharingKey
98
-                    }
99
-                ]
100
-            };
101
-        } else if (RTCBrowserType.isFirefox()) {
102
-            constraints.video = {
103
-                mozMediaSource: "window",
104
-                mediaSource: "window"
105
-            };
106
-
107
-        } else {
108
-            console.error(
109
-                "'screen' WebRTC media source is supported only in Chrome" +
110
-                " and with Temasys plugin");
111
-        }
112
-    }
113
-    if (um.indexOf('desktop') >= 0) {
114
-        constraints.video = {
115
-            mandatory: {
116
-                chromeMediaSource: "desktop",
117
-                chromeMediaSourceId: desktopStream,
118
-                googLeakyBucket: true,
119
-                maxWidth: window.screen.width,
120
-                maxHeight: window.screen.height,
121
-                maxFrameRate: 3
122
-            },
123
-            optional: []
124
-        };
125
-    }
126
-
127
-    if (bandwidth) {
128
-        if (!constraints.video) {
129
-            //same behaviour as true
130
-            constraints.video = {mandatory: {}, optional: []};
131
-        }
132
-        constraints.video.optional.push({bandwidth: bandwidth});
133
-    }
134
-    if (fps) {
135
-        // for some cameras it might be necessary to request 30fps
136
-        // so they choose 30fps mjpg over 10fps yuy2
137
-        if (!constraints.video) {
138
-            // same behaviour as true;
139
-            constraints.video = {mandatory: {}, optional: []};
140
-        }
141
-        constraints.video.mandatory.minFrameRate = fps;
142
-    }
143
-
144
-    // we turn audio for both audio and video tracks, the fake audio & video seems to work
145
-    // only when enabled in one getUserMedia call, we cannot get fake audio separate by fake video
146
-    // this later can be a problem with some of the tests
147
-    if(RTCBrowserType.isFirefox() && config.firefox_fake_device)
148
-    {
149
-        constraints.audio = true;
150
-        constraints.fake = true;
151
-    }
152
-
153
-    return constraints;
154
-}
155
-
156
-
157
-function RTCUtils(RTCService, eventEmitter, onTemasysPluginReady)
158
-{
159
-    var self = this;
160
-    this.service = RTCService;
161
-    this.eventEmitter = eventEmitter;
162
-    if (RTCBrowserType.isFirefox()) {
163
-        var FFversion = RTCBrowserType.getFirefoxVersion();
164
-        if (FFversion >= 40) {
165
-            this.peerconnection = mozRTCPeerConnection;
166
-            this.getUserMedia = navigator.mozGetUserMedia.bind(navigator);
167
-            this.pc_constraints = {};
168
-            this.attachMediaStream =  function (element, stream) {
169
-                //  srcObject is being standardized and FF will eventually
170
-                //  support that unprefixed. FF also supports the
171
-                //  "element.src = URL.createObjectURL(...)" combo, but that
172
-                //  will be deprecated in favour of srcObject.
173
-                //
174
-                // https://groups.google.com/forum/#!topic/mozilla.dev.media/pKOiioXonJg
175
-                // https://github.com/webrtc/samples/issues/302
176
-                if(!element[0])
177
-                    return;
178
-                element[0].mozSrcObject = stream;
179
-                element[0].play();
180
-            };
181
-            this.getStreamID =  function (stream) {
182
-                var id = stream.id;
183
-                if (!id) {
184
-                    var tracks = stream.getVideoTracks();
185
-                    if (!tracks || tracks.length === 0) {
186
-                        tracks = stream.getAudioTracks();
187
-                    }
188
-                    id = tracks[0].id;
189
-                }
190
-                return APP.xmpp.filter_special_chars(id);
191
-            };
192
-            this.getVideoSrc = function (element) {
193
-                if(!element)
194
-                    return null;
195
-                return element.mozSrcObject;
196
-            };
197
-            this.setVideoSrc = function (element, src) {
198
-                if(element)
199
-                    element.mozSrcObject = src;
200
-            };
201
-            window.RTCSessionDescription = mozRTCSessionDescription;
202
-            window.RTCIceCandidate = mozRTCIceCandidate;
203
-        } else {
204
-            console.error(
205
-                "Firefox version too old: " + FFversion + ". Required >= 40.");
206
-            window.location.href = 'unsupported_browser.html';
207
-            return;
208
-        }
209
-
210
-    } else if (RTCBrowserType.isChrome() || RTCBrowserType.isOpera()) {
211
-        this.peerconnection = webkitRTCPeerConnection;
212
-        this.getUserMedia = navigator.webkitGetUserMedia.bind(navigator);
213
-        this.attachMediaStream = function (element, stream) {
214
-            element.attr('src', webkitURL.createObjectURL(stream));
215
-        };
216
-        this.getStreamID = function (stream) {
217
-            // streams from FF endpoints have the characters '{' and '}'
218
-            // that make jQuery choke.
219
-            return APP.xmpp.filter_special_chars(stream.id);
220
-        };
221
-        this.getVideoSrc = function (element) {
222
-            if(!element)
223
-                return null;
224
-            return element.getAttribute("src");
225
-        };
226
-        this.setVideoSrc = function (element, src) {
227
-            if(element)
228
-                element.setAttribute("src", src);
229
-        };
230
-        // DTLS should now be enabled by default but..
231
-        this.pc_constraints = {'optional': [{'DtlsSrtpKeyAgreement': 'true'}]};
232
-        if (RTCBrowserType.isAndroid()) {
233
-            this.pc_constraints = {}; // disable DTLS on Android
234
-        }
235
-        if (!webkitMediaStream.prototype.getVideoTracks) {
236
-            webkitMediaStream.prototype.getVideoTracks = function () {
237
-                return this.videoTracks;
238
-            };
239
-        }
240
-        if (!webkitMediaStream.prototype.getAudioTracks) {
241
-            webkitMediaStream.prototype.getAudioTracks = function () {
242
-                return this.audioTracks;
243
-            };
244
-        }
245
-    }
246
-    // Detect IE/Safari
247
-    else if (RTCBrowserType.isTemasysPluginUsed()) {
248
-
249
-        //AdapterJS.WebRTCPlugin.setLogLevel(
250
-        //    AdapterJS.WebRTCPlugin.PLUGIN_LOG_LEVELS.VERBOSE);
251
-
252
-        AdapterJS.webRTCReady(function (isPlugin) {
253
-
254
-            self.peerconnection = RTCPeerConnection;
255
-            self.getUserMedia = getUserMedia;
256
-            self.attachMediaStream = function (elSel, stream) {
257
-
258
-                if (stream.id === "dummyAudio" || stream.id === "dummyVideo") {
259
-                    return;
260
-                }
261
-
262
-                attachMediaStream(elSel[0], stream);
263
-            };
264
-            self.getStreamID = function (stream) {
265
-                return APP.xmpp.filter_special_chars(stream.label);
266
-            };
267
-            self.getVideoSrc = function (element) {
268
-                if (!element) {
269
-                    console.warn("Attempt to get video SRC of null element");
270
-                    return null;
271
-                }
272
-                var children = element.children;
273
-                for (var i = 0; i !== children.length; ++i) {
274
-                    if (children[i].name === 'streamId') {
275
-                        return children[i].value;
276
-                    }
277
-                }
278
-                //console.info(element.id + " SRC: " + src);
279
-                return null;
280
-            };
281
-            self.setVideoSrc = function (element, src) {
282
-                //console.info("Set video src: ", element, src);
283
-                if (!src) {
284
-                    console.warn("Not attaching video stream, 'src' is null");
285
-                    return;
286
-                }
287
-                AdapterJS.WebRTCPlugin.WaitForPluginReady();
288
-                var stream = AdapterJS.WebRTCPlugin.plugin
289
-                    .getStreamWithId(AdapterJS.WebRTCPlugin.pageId, src);
290
-                attachMediaStream(element, stream);
291
-            };
292
-
293
-            onTemasysPluginReady(isPlugin);
294
-        });
295
-    } else {
296
-        try {
297
-            console.log('Browser does not appear to be WebRTC-capable');
298
-        } catch (e) { }
299
-        window.location.href = 'unsupported_browser.html';
300
-    }
301
-}
302
-
303
-
304
-RTCUtils.prototype.getUserMediaWithConstraints = function(
305
-    um, success_callback, failure_callback, resolution,bandwidth, fps,
306
-    desktopStream) {
307
-    currentResolution = resolution;
308
-
309
-    var constraints = getConstraints(
310
-        um, resolution, bandwidth, fps, desktopStream);
311
-
312
-    console.info("Get media constraints", constraints);
313
-
314
-    var self = this;
315
-
316
-    try {
317
-        this.getUserMedia(constraints,
318
-            function (stream) {
319
-                console.log('onUserMediaSuccess');
320
-                self.setAvailableDevices(um, true);
321
-                success_callback(stream);
322
-            },
323
-            function (error) {
324
-                self.setAvailableDevices(um, false);
325
-                console.warn('Failed to get access to local media. Error ',
326
-                    error, constraints);
327
-                self.eventEmitter.emit(RTCEvents.GET_USER_MEDIA_FAILED, error);
328
-                if (failure_callback) {
329
-                    failure_callback(error);
330
-                }
331
-            });
332
-    } catch (e) {
333
-        console.error('GUM failed: ', e);
334
-        self.eventEmitter.emit(RTCEvents.GET_USER_MEDIA_FAILED, e);
335
-        if(failure_callback) {
336
-            failure_callback(e);
337
-        }
338
-    }
339
-};
340
-
341
-RTCUtils.prototype.setAvailableDevices = function (um, available) {
342
-    var devices = {};
343
-    if(um.indexOf("video") != -1) {
344
-        devices.video = available;
345
-    }
346
-    if(um.indexOf("audio") != -1) {
347
-        devices.audio = available;
348
-    }
349
-    this.service.setDeviceAvailability(devices);
350
-};
351
-
352
-/**
353
- * We ask for audio and video combined stream in order to get permissions and
354
- * not to ask twice.
355
- */
356
-RTCUtils.prototype.obtainAudioAndVideoPermissions =
357
-    function(devices, callback, usageOptions)
358
-{
359
-    var self = this;
360
-    // Get AV
361
-
362
-    var successCallback = function (stream) {
363
-        if(callback)
364
-            callback(stream, usageOptions);
365
-        else
366
-            self.successCallback(stream, usageOptions);
367
-    };
368
-
369
-    if(!devices)
370
-        devices = ['audio', 'video'];
371
-
372
-    var newDevices = [];
373
-
374
-
375
-    if(usageOptions)
376
-        for(var i = 0; i < devices.length; i++) {
377
-            var device = devices[i];
378
-            if(usageOptions[device] === true)
379
-                newDevices.push(device);
380
-        }
381
-    else
382
-        newDevices = devices;
383
-
384
-    if(newDevices.length === 0) {
385
-        successCallback();
386
-        return;
387
-    }
388
-
389
-    if (RTCBrowserType.isFirefox() || RTCBrowserType.isTemasysPluginUsed()) {
390
-
391
-        // With FF/IE we can't split the stream into audio and video because FF
392
-        // doesn't support media stream constructors. So, we need to get the
393
-        // audio stream separately from the video stream using two distinct GUM
394
-        // calls. Not very user friendly :-( but we don't have many other
395
-        // options neither.
396
-        //
397
-        // Note that we pack those 2 streams in a single object and pass it to
398
-        // the successCallback method.
399
-        var obtainVideo = function (audioStream) {
400
-            self.getUserMediaWithConstraints(
401
-                ['video'],
402
-                function (videoStream) {
403
-                    return successCallback({
404
-                        audioStream: audioStream,
405
-                        videoStream: videoStream
406
-                    });
407
-                },
408
-                function (error) {
409
-                    console.error(
410
-                        'failed to obtain video stream - stop', error);
411
-                    self.errorCallback(error);
412
-                },
413
-                config.resolution || '360');
414
-        };
415
-        var obtainAudio = function () {
416
-            self.getUserMediaWithConstraints(
417
-                ['audio'],
418
-                function (audioStream) {
419
-                    if (newDevices.indexOf('video') !== -1)
420
-                        obtainVideo(audioStream);
421
-                },
422
-                function (error) {
423
-                    console.error(
424
-                        'failed to obtain audio stream - stop', error);
425
-                    self.errorCallback(error);
426
-                }
427
-            );
428
-        };
429
-        if (newDevices.indexOf('audio') !== -1) {
430
-            obtainAudio();
431
-        } else {
432
-            obtainVideo(null);
433
-        }
434
-    } else {
435
-        this.getUserMediaWithConstraints(
436
-        newDevices,
437
-        function (stream) {
438
-            successCallback(stream);
439
-        },
440
-        function (error) {
441
-            self.errorCallback(error);
442
-        },
443
-        config.resolution || '360');
444
-    }
445
-};
446
-
447
-RTCUtils.prototype.successCallback = function (stream, usageOptions) {
448
-    // If this is FF or IE, the stream parameter is *not* a MediaStream object,
449
-    // it's an object with two properties: audioStream, videoStream.
450
-    if (stream && stream.getAudioTracks && stream.getVideoTracks)
451
-        console.log('got', stream, stream.getAudioTracks().length,
452
-            stream.getVideoTracks().length);
453
-    this.handleLocalStream(stream, usageOptions);
454
-};
455
-
456
-RTCUtils.prototype.errorCallback = function (error) {
457
-    var self = this;
458
-    console.error('failed to obtain audio/video stream - trying audio only', error);
459
-    var resolution = getPreviousResolution(currentResolution);
460
-    if(typeof error == "object" && error.constraintName && error.name
461
-        && (error.name == "ConstraintNotSatisfiedError" ||
462
-            error.name == "OverconstrainedError") &&
463
-        (error.constraintName == "minWidth" || error.constraintName == "maxWidth" ||
464
-            error.constraintName == "minHeight" || error.constraintName == "maxHeight")
465
-        && resolution)
466
-    {
467
-        self.getUserMediaWithConstraints(['audio', 'video'],
468
-            function (stream) {
469
-                return self.successCallback(stream);
470
-            }, function (error) {
471
-                return self.errorCallback(error);
472
-            }, resolution);
473
-    }
474
-    else {
475
-        self.getUserMediaWithConstraints(
476
-            ['audio'],
477
-            function (stream) {
478
-                return self.successCallback(stream);
479
-            },
480
-            function (error) {
481
-                console.error('failed to obtain audio/video stream - stop',
482
-                    error);
483
-                return self.successCallback(null);
484
-            }
485
-        );
486
-    }
487
-};
488
-
489
-RTCUtils.prototype.handleLocalStream = function(stream, usageOptions) {
490
-    // If this is FF, the stream parameter is *not* a MediaStream object, it's
491
-    // an object with two properties: audioStream, videoStream.
492
-    var audioStream, videoStream;
493
-    if(window.webkitMediaStream)
494
-    {
495
-        audioStream = new webkitMediaStream();
496
-        videoStream = new webkitMediaStream();
497
-        if(stream) {
498
-            var audioTracks = stream.getAudioTracks();
499
-
500
-            for (var i = 0; i < audioTracks.length; i++) {
501
-                audioStream.addTrack(audioTracks[i]);
502
-            }
503
-
504
-            var videoTracks = stream.getVideoTracks();
505
-
506
-            for (i = 0; i < videoTracks.length; i++) {
507
-                videoStream.addTrack(videoTracks[i]);
508
-            }
509
-        }
510
-    }
511
-    else if (RTCBrowserType.isFirefox() || RTCBrowserType.isTemasysPluginUsed())
512
-    {   // Firefox and Temasys plugin
513
-        if (stream && stream.audioStream)
514
-            audioStream = stream.audioStream;
515
-        else
516
-            audioStream = new DummyMediaStream("dummyAudio");
517
-
518
-        if (stream && stream.videoStream)
519
-            videoStream = stream.videoStream;
520
-        else
521
-            videoStream = new DummyMediaStream("dummyVideo");
522
-    }
523
-
524
-    var audioMuted = (usageOptions && usageOptions.audio === false),
525
-        videoMuted = (usageOptions && usageOptions.video === false);
526
-
527
-    var audioGUM = (!usageOptions || usageOptions.audio !== false),
528
-        videoGUM = (!usageOptions || usageOptions.video !== false);
529
-
530
-
531
-    this.service.createLocalStream(
532
-        audioStream, MediaStreamType.AUDIO_TYPE, null, null,
533
-        audioMuted, audioGUM);
534
-
535
-    this.service.createLocalStream(
536
-        videoStream, MediaStreamType.VIDEO_TYPE, null, 'camera',
537
-        videoMuted, videoGUM);
538
-};
539
-
540
-function DummyMediaStream(id) {
541
-    this.id = id;
542
-    this.label = id;
543
-    this.stop = function() { };
544
-    this.getAudioTracks = function() { return []; };
545
-    this.getVideoTracks = function() { return []; };
546
-}
547
-
548
-RTCUtils.prototype.createStream = function(stream, isVideo) {
549
-    var newStream = null;
550
-    if (window.webkitMediaStream) {
551
-        newStream = new webkitMediaStream();
552
-        if (newStream) {
553
-            var tracks = (isVideo ? stream.getVideoTracks() : stream.getAudioTracks());
554
-
555
-            for (var i = 0; i < tracks.length; i++) {
556
-                newStream.addTrack(tracks[i]);
557
-            }
558
-        }
559
-
560
-    }
561
-    else {
562
-        // FIXME: this is duplicated with 'handleLocalStream' !!!
563
-        if (stream) {
564
-            newStream = stream;
565
-        } else {
566
-            newStream =
567
-                new DummyMediaStream(isVideo ? "dummyVideo" : "dummyAudio");
568
-        }
569
-    }
570
-
571
-    return newStream;
572
-};
573
-
574
-module.exports = RTCUtils;

+ 0
- 1168
modules/RTC/adapter.screenshare.js
ファイル差分が大きすぎるため省略します
ファイルの表示


+ 1
- 2
modules/UI/videolayout/VideoLayout.js ファイルの表示

@@ -12,7 +12,6 @@ import LargeVideoManager, {VideoContainerType} from "./LargeVideo";
12 12
 import {PreziContainerType} from '../prezi/Prezi';
13 13
 import LocalVideo from "./LocalVideo";
14 14
 
15
-var MediaStreamType = require("../../../service/RTC/MediaStreamTypes");
16 15
 var RTCBrowserType = require('../../RTC/RTCBrowserType');
17 16
 
18 17
 var remoteVideos = {};
@@ -674,7 +673,7 @@ var VideoLayout = {
674 673
 
675 674
                     var jid = APP.xmpp.findJidFromResource(resourceJid);
676 675
                     var mediaStream =
677
-                        APP.RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE];
676
+                        APP.RTC.remoteStreams[jid]['video'];
678 677
                     var sel = remoteVideo.selectVideoElement();
679 678
 
680 679
                     APP.RTC.attachMediaStream(sel, mediaStream.stream);

+ 0
- 127
modules/xmpp/JingleSession.js ファイルの表示

@@ -1,127 +0,0 @@
1
-/*
2
- * JingleSession provides an API to manage a single Jingle session. We will
3
- * have different implementations depending on the underlying interface used
4
- * (i.e. WebRTC and ORTC) and here we hold the code common to all of them.
5
- */
6
-function JingleSession(me, sid, connection, service, eventEmitter) {
7
-    /**
8
-     * Our JID.
9
-     */
10
-    this.me = me;
11
-
12
-    /**
13
-     * The Jingle session identifier.
14
-     */
15
-    this.sid = sid;
16
-
17
-    /**
18
-     * The XMPP connection.
19
-     */
20
-    this.connection = connection;
21
-
22
-    /**
23
-     * The XMPP service.
24
-     */
25
-    this.service = service;
26
-
27
-    /**
28
-     * The event emitter.
29
-     */
30
-    this.eventEmitter = eventEmitter;
31
-
32
-    /**
33
-     * Whether to use dripping or not. Dripping is sending trickle candidates
34
-     * not one-by-one.
35
-     * Note: currently we do not support 'false'.
36
-     */
37
-    this.usedrip = true;
38
-
39
-    /**
40
-     *  When dripping is used, stores ICE candidates which are to be sent.
41
-     */
42
-    this.drip_container = [];
43
-
44
-    // Media constraints. Is this WebRTC only?
45
-    this.media_constraints = null;
46
-
47
-    // ICE servers config (RTCConfiguration?).
48
-    this.ice_config = {};
49
-}
50
-
51
-/**
52
- * Prepares this object to initiate a session.
53
- * @param peerjid the JID of the remote peer.
54
- * @param isInitiator whether we will be the Jingle initiator.
55
- * @param media_constraints
56
- * @param ice_config
57
- */
58
-JingleSession.prototype.initialize = function(peerjid, isInitiator,
59
-                                              media_constraints, ice_config) {
60
-    this.media_constraints = media_constraints;
61
-    this.ice_config = ice_config;
62
-
63
-    if (this.state !== null) {
64
-        console.error('attempt to initiate on session ' + this.sid +
65
-        'in state ' + this.state);
66
-        return;
67
-    }
68
-    this.state = 'pending';
69
-    this.initiator = isInitiator ? this.me : peerjid;
70
-    this.responder = !isInitiator ? this.me : peerjid;
71
-    this.peerjid = peerjid;
72
-
73
-    this.doInitialize();
74
-};
75
-
76
-/**
77
- * Finishes initialization.
78
- */
79
-JingleSession.prototype.doInitialize = function() {};
80
-
81
-/**
82
- * Adds the ICE candidates found in the 'contents' array as remote candidates?
83
- * Note: currently only used on transport-info
84
- */
85
-JingleSession.prototype.addIceCandidates = function(contents) {};
86
-
87
-/**
88
- * Handles an 'add-source' event.
89
- *
90
- * @param contents an array of Jingle 'content' elements.
91
- */
92
-JingleSession.prototype.addSources = function(contents) {};
93
-
94
-/**
95
- * Handles a 'remove-source' event.
96
- *
97
- * @param contents an array of Jingle 'content' elements.
98
- */
99
-JingleSession.prototype.removeSources = function(contents) {};
100
-
101
-/**
102
- * Terminates this Jingle session (stops sending media and closes the streams?)
103
- */
104
-JingleSession.prototype.terminate = function() {};
105
-
106
-/**
107
- * Sends a Jingle session-terminate message to the peer and terminates the
108
- * session.
109
- * @param reason
110
- * @param text
111
- */
112
-JingleSession.prototype.sendTerminate = function(reason, text) {};
113
-
114
-/**
115
- * Handles an offer from the remote peer (prepares to accept a session).
116
- * @param jingle the 'jingle' XML element.
117
- */
118
-JingleSession.prototype.setOffer = function(jingle) {};
119
-
120
-/**
121
- * Handles an answer from the remote peer (prepares to accept a session).
122
- * @param jingle the 'jingle' XML element.
123
- */
124
-JingleSession.prototype.setAnswer = function(jingle) {};
125
-
126
-
127
-module.exports = JingleSession;

+ 0
- 1520
modules/xmpp/JingleSessionPC.js
ファイル差分が大きすぎるため省略します
ファイルの表示


+ 0
- 268
modules/xmpp/LocalSSRCReplacement.js ファイルの表示

@@ -1,268 +0,0 @@
1
-/* global $ */
2
-
3
-/*
4
- Here we do modifications of local video SSRCs. There are 2 situations we have
5
- to handle:
6
-
7
- 1. We generate SSRC for local recvonly video stream. This is the case when we
8
-    have no local camera and it is not generated automatically, but SSRC=1 is
9
-    used implicitly. If that happens RTCP packets will be dropped by the JVB
10
-    and we won't be able to request video key frames correctly.
11
-
12
- 2. A hack to re-use SSRC of the first video stream for any new stream created
13
-    in future. It turned out that Chrome may keep on using the SSRC of removed
14
-    video stream in RTCP even though a new one has been created. So we just
15
-    want to avoid that by re-using it. Jingle 'source-remove'/'source-add'
16
-    notifications are blocked once first video SSRC is advertised to the focus.
17
-
18
- What this hack does:
19
-
20
- 1. Stores the SSRC of the first video stream created by
21
-   a) scanning Jingle session-accept/session-invite for existing video SSRC
22
-   b) watching for 'source-add' for new video stream if it has not been
23
-      created in step a)
24
- 2. Exposes method 'mungeLocalVideoSSRC' which replaces any new video SSRC with
25
-    the stored one. It is called by 'TracablePeerConnection' before local SDP is
26
-    returned to the other parts of the application.
27
- 3. Scans 'source-remove'/'source-add' notifications for stored video SSRC and
28
-    blocks those notifications. This makes Jicofo and all participants think
29
-    that it exists all the time even if the video stream has been removed or
30
-    replaced locally. Thanks to that there is no additional signaling activity
31
-    on video mute or when switching to the desktop stream.
32
- */
33
-
34
-var SDP = require('./SDP');
35
-var RTCBrowserType = require('../RTC/RTCBrowserType');
36
-
37
-/**
38
- * The hack is enabled on all browsers except FF by default
39
- * FIXME finish the hack once removeStream method is implemented in FF
40
- * @type {boolean}
41
- */
42
-var isEnabled = !RTCBrowserType.isFirefox();
43
-
44
-/**
45
- * Stored SSRC of local video stream.
46
- */
47
-var localVideoSSRC;
48
-
49
-/**
50
- * SSRC used for recvonly video stream when we have no local camera.
51
- * This is in order to tell Chrome what SSRC should be used in RTCP requests
52
- * instead of 1.
53
- */
54
-var localRecvOnlySSRC;
55
-
56
-/**
57
- * cname for <tt>localRecvOnlySSRC</tt>
58
- */
59
-var localRecvOnlyCName;
60
-
61
-/**
62
- * Method removes <source> element which describes <tt>localVideoSSRC</tt>
63
- * from given Jingle IQ.
64
- * @param modifyIq 'source-add' or 'source-remove' Jingle IQ.
65
- * @param actionName display name of the action which will be printed in log
66
- *        messages.
67
- * @returns {*} modified Jingle IQ, so that it does not contain <source> element
68
- *          corresponding to <tt>localVideoSSRC</tt> or <tt>null</tt> if no
69
- *          other SSRCs left to be signaled after removing it.
70
- */
71
-var filterOutSource = function (modifyIq, actionName) {
72
-    var modifyIqTree = $(modifyIq.tree());
73
-
74
-    if (!localVideoSSRC)
75
-        return modifyIqTree[0];
76
-
77
-    var videoSSRC = modifyIqTree.find(
78
-        '>jingle>content[name="video"]' +
79
-        '>description>source[ssrc="' + localVideoSSRC + '"]');
80
-
81
-    if (!videoSSRC.length) {
82
-        return modifyIqTree[0];
83
-    }
84
-
85
-    console.info(
86
-        'Blocking ' + actionName + ' for local video SSRC: ' + localVideoSSRC);
87
-
88
-    videoSSRC.remove();
89
-
90
-    // Check if any sources still left to be added/removed
91
-    if (modifyIqTree.find('>jingle>content>description>source').length) {
92
-        return modifyIqTree[0];
93
-    } else {
94
-        return null;
95
-    }
96
-};
97
-
98
-/**
99
- * Scans given Jingle IQ for video SSRC and stores it.
100
- * @param jingleIq the Jingle IQ to be scanned for video SSRC.
101
- */
102
-var storeLocalVideoSSRC = function (jingleIq) {
103
-    var videoSSRCs =
104
-        $(jingleIq.tree())
105
-            .find('>jingle>content[name="video"]>description>source');
106
-
107
-    videoSSRCs.each(function (idx, ssrcElem) {
108
-        if (localVideoSSRC)
109
-            return;
110
-        // We consider SSRC real only if it has msid attribute
111
-        // recvonly streams in FF do not have it as well as local SSRCs
112
-        // we generate for recvonly streams in Chrome
113
-        var ssrSel = $(ssrcElem);
114
-        var msid = ssrSel.find('>parameter[name="msid"]');
115
-        if (msid.length) {
116
-            var ssrcVal = ssrSel.attr('ssrc');
117
-            if (ssrcVal) {
118
-                localVideoSSRC = ssrcVal;
119
-                console.info('Stored local video SSRC' +
120
-                             ' for future re-use: ' + localVideoSSRC);
121
-            }
122
-        }
123
-    });
124
-};
125
-
126
-/**
127
- * Generates new SSRC for local video recvonly stream.
128
- * FIXME what about eventual SSRC collision ?
129
- */
130
-function generateRecvonlySSRC() {
131
-    //
132
-    localRecvOnlySSRC =
133
-        Math.random().toString(10).substring(2, 11);
134
-    localRecvOnlyCName =
135
-        Math.random().toString(36).substring(2);
136
-    console.info(
137
-        "Generated local recvonly SSRC: " + localRecvOnlySSRC +
138
-        ", cname: " + localRecvOnlyCName);
139
-}
140
-
141
-var LocalSSRCReplacement = {
142
-    /**
143
-     * Method must be called before 'session-initiate' or 'session-invite' is
144
-     * sent. Scans the IQ for local video SSRC and stores it if detected.
145
-     *
146
-     * @param sessionInit our 'session-initiate' or 'session-accept' Jingle IQ
147
-     *        which will be scanned for local video SSRC.
148
-     */
149
-    processSessionInit: function (sessionInit) {
150
-        if (!isEnabled)
151
-            return;
152
-
153
-        if (localVideoSSRC) {
154
-            console.error("Local SSRC stored already: " + localVideoSSRC);
155
-            return;
156
-        }
157
-        storeLocalVideoSSRC(sessionInit);
158
-    },
159
-    /**
160
-     * If we have local video SSRC stored searched given
161
-     * <tt>localDescription</tt> for video SSRC and makes sure it is replaced
162
-     * with the stored one.
163
-     * @param localDescription local description object that will have local
164
-     *        video SSRC replaced with the stored one
165
-     * @returns modified <tt>localDescription</tt> object.
166
-     */
167
-    mungeLocalVideoSSRC: function (localDescription) {
168
-        if (!isEnabled)
169
-            return localDescription;
170
-
171
-        if (!localDescription) {
172
-            console.warn("localDescription is null or undefined");
173
-            return localDescription;
174
-        }
175
-
176
-        // IF we have local video SSRC stored make sure it is replaced
177
-        // with old SSRC
178
-        if (localVideoSSRC) {
179
-            var newSdp = new SDP(localDescription.sdp);
180
-            if (newSdp.media[1].indexOf("a=ssrc:") !== -1 &&
181
-                !newSdp.containsSSRC(localVideoSSRC)) {
182
-                // Get new video SSRC
183
-                var map = newSdp.getMediaSsrcMap();
184
-                var videoPart = map[1];
185
-                var videoSSRCs = videoPart.ssrcs;
186
-                var newSSRC = Object.keys(videoSSRCs)[0];
187
-
188
-                console.info(
189
-                    "Replacing new video SSRC: " + newSSRC +
190
-                    " with " + localVideoSSRC);
191
-
192
-                localDescription.sdp =
193
-                    newSdp.raw.replace(
194
-                        new RegExp('a=ssrc:' + newSSRC, 'g'),
195
-                        'a=ssrc:' + localVideoSSRC);
196
-            }
197
-        } else {
198
-            // Make sure we have any SSRC for recvonly video stream
199
-            var sdp = new SDP(localDescription.sdp);
200
-
201
-            if (sdp.media[1] && sdp.media[1].indexOf('a=ssrc:') === -1 &&
202
-                sdp.media[1].indexOf('a=recvonly') !== -1) {
203
-
204
-                if (!localRecvOnlySSRC) {
205
-                    generateRecvonlySSRC();
206
-                }
207
-
208
-                console.info('No SSRC in video recvonly stream' +
209
-                             ' - adding SSRC: ' + localRecvOnlySSRC);
210
-
211
-                sdp.media[1] += 'a=ssrc:' + localRecvOnlySSRC +
212
-                                ' cname:' + localRecvOnlyCName + '\r\n';
213
-
214
-                localDescription.sdp = sdp.session + sdp.media.join('');
215
-            }
216
-        }
217
-        return localDescription;
218
-    },
219
-    /**
220
-     * Method must be called before 'source-add' notification is sent. In case
221
-     * we have local video SSRC advertised already it will be removed from the
222
-     * notification. If no other SSRCs are described by given IQ null will be
223
-     * returned which means that there is no point in sending the notification.
224
-     * @param sourceAdd 'source-add' Jingle IQ to be processed
225
-     * @returns modified 'source-add' IQ which can be sent to the focus or
226
-     *          <tt>null</tt> if no notification shall be sent. It is no longer
227
-     *          a Strophe IQ Builder instance, but DOM element tree.
228
-     */
229
-    processSourceAdd: function (sourceAdd) {
230
-        if (!isEnabled)
231
-            return sourceAdd;
232
-
233
-        if (!localVideoSSRC) {
234
-            // Store local SSRC if available
235
-            storeLocalVideoSSRC(sourceAdd);
236
-            return sourceAdd;
237
-        } else {
238
-            return filterOutSource(sourceAdd, 'source-add');
239
-        }
240
-    },
241
-    /**
242
-     * Method must be called before 'source-remove' notification is sent.
243
-     * Removes local video SSRC from the notification. If there are no other
244
-     * SSRCs described in the given IQ <tt>null</tt> will be returned which
245
-     * means that there is no point in sending the notification.
246
-     * @param sourceRemove 'source-remove' Jingle IQ to be processed
247
-     * @returns modified 'source-remove' IQ which can be sent to the focus or
248
-     *          <tt>null</tt> if no notification shall be sent. It is no longer
249
-     *          a Strophe IQ Builder instance, but DOM element tree.
250
-     */
251
-    processSourceRemove: function (sourceRemove) {
252
-        if (!isEnabled)
253
-            return sourceRemove;
254
-
255
-        return filterOutSource(sourceRemove, 'source-remove');
256
-    },
257
-
258
-    /**
259
-     * Turns the hack on or off
260
-     * @param enabled <tt>true</tt> to enable the hack or <tt>false</tt>
261
-     *                to disable it
262
-     */
263
-    setEnabled: function (enabled) {
264
-        isEnabled = enabled;
265
-    }
266
-};
267
-
268
-module.exports = LocalSSRCReplacement;

+ 0
- 645
modules/xmpp/SDP.js ファイルの表示

@@ -1,645 +0,0 @@
1
-/* jshint -W101 */
2
-/* jshint -W117 */
3
-var SDPUtil = require("./SDPUtil");
4
-
5
-// SDP STUFF
6
-function SDP(sdp) {
7
-    /**
8
-     * Whether or not to remove TCP ice candidates when translating from/to jingle.
9
-     * @type {boolean}
10
-     */
11
-    this.removeTcpCandidates = false;
12
-
13
-    /**
14
-     * Whether or not to remove UDP ice candidates when translating from/to jingle.
15
-     * @type {boolean}
16
-     */
17
-    this.removeUdpCandidates = false;
18
-
19
-    this.media = sdp.split('\r\nm=');
20
-    for (var i = 1; i < this.media.length; i++) {
21
-        this.media[i] = 'm=' + this.media[i];
22
-        if (i != this.media.length - 1) {
23
-            this.media[i] += '\r\n';
24
-        }
25
-    }
26
-    this.session = this.media.shift() + '\r\n';
27
-    this.raw = this.session + this.media.join('');
28
-}
29
-
30
-/**
31
- * Returns map of MediaChannel mapped per channel idx.
32
- */
33
-SDP.prototype.getMediaSsrcMap = function() {
34
-    var self = this;
35
-    var media_ssrcs = {};
36
-    var tmp;
37
-    for (var mediaindex = 0; mediaindex < self.media.length; mediaindex++) {
38
-        tmp = SDPUtil.find_lines(self.media[mediaindex], 'a=ssrc:');
39
-        var mid = SDPUtil.parse_mid(SDPUtil.find_line(self.media[mediaindex], 'a=mid:'));
40
-        var media = {
41
-            mediaindex: mediaindex,
42
-            mid: mid,
43
-            ssrcs: {},
44
-            ssrcGroups: []
45
-        };
46
-        media_ssrcs[mediaindex] = media;
47
-        tmp.forEach(function (line) {
48
-            var linessrc = line.substring(7).split(' ')[0];
49
-            // allocate new ChannelSsrc
50
-            if(!media.ssrcs[linessrc]) {
51
-                media.ssrcs[linessrc] = {
52
-                    ssrc: linessrc,
53
-                    lines: []
54
-                };
55
-            }
56
-            media.ssrcs[linessrc].lines.push(line);
57
-        });
58
-        tmp = SDPUtil.find_lines(self.media[mediaindex], 'a=ssrc-group:');
59
-        tmp.forEach(function(line){
60
-            var idx = line.indexOf(' ');
61
-            var semantics = line.substr(0, idx).substr(13);
62
-            var ssrcs = line.substr(14 + semantics.length).split(' ');
63
-            if (ssrcs.length) {
64
-                media.ssrcGroups.push({
65
-                    semantics: semantics,
66
-                    ssrcs: ssrcs
67
-                });
68
-            }
69
-        });
70
-    }
71
-    return media_ssrcs;
72
-};
73
-/**
74
- * Returns <tt>true</tt> if this SDP contains given SSRC.
75
- * @param ssrc the ssrc to check.
76
- * @returns {boolean} <tt>true</tt> if this SDP contains given SSRC.
77
- */
78
-SDP.prototype.containsSSRC = function(ssrc) {
79
-    var medias = this.getMediaSsrcMap();
80
-    Object.keys(medias).forEach(function(mediaindex){
81
-        var media = medias[mediaindex];
82
-        //console.log("Check", channel, ssrc);
83
-        if(Object.keys(media.ssrcs).indexOf(ssrc) != -1){
84
-            return true;
85
-        }
86
-    });
87
-    return false;
88
-};
89
-
90
-// remove iSAC and CN from SDP
91
-SDP.prototype.mangle = function () {
92
-    var i, j, mline, lines, rtpmap, newdesc;
93
-    for (i = 0; i < this.media.length; i++) {
94
-        lines = this.media[i].split('\r\n');
95
-        lines.pop(); // remove empty last element
96
-        mline = SDPUtil.parse_mline(lines.shift());
97
-        if (mline.media != 'audio')
98
-            continue;
99
-        newdesc = '';
100
-        mline.fmt.length = 0;
101
-        for (j = 0; j < lines.length; j++) {
102
-            if (lines[j].substr(0, 9) == 'a=rtpmap:') {
103
-                rtpmap = SDPUtil.parse_rtpmap(lines[j]);
104
-                if (rtpmap.name == 'CN' || rtpmap.name == 'ISAC')
105
-                    continue;
106
-                mline.fmt.push(rtpmap.id);
107
-                newdesc += lines[j] + '\r\n';
108
-            } else {
109
-                newdesc += lines[j] + '\r\n';
110
-            }
111
-        }
112
-        this.media[i] = SDPUtil.build_mline(mline) + '\r\n';
113
-        this.media[i] += newdesc;
114
-    }
115
-    this.raw = this.session + this.media.join('');
116
-};
117
-
118
-// remove lines matching prefix from session section
119
-SDP.prototype.removeSessionLines = function(prefix) {
120
-    var self = this;
121
-    var lines = SDPUtil.find_lines(this.session, prefix);
122
-    lines.forEach(function(line) {
123
-        self.session = self.session.replace(line + '\r\n', '');
124
-    });
125
-    this.raw = this.session + this.media.join('');
126
-    return lines;
127
-};
128
-
129
-// remove lines matching prefix from a media section specified by mediaindex
130
-// TODO: non-numeric mediaindex could match mid
131
-SDP.prototype.removeMediaLines = function(mediaindex, prefix) {
132
-    var self = this;
133
-    var lines = SDPUtil.find_lines(this.media[mediaindex], prefix);
134
-    lines.forEach(function(line) {
135
-        self.media[mediaindex] = self.media[mediaindex].replace(line + '\r\n', '');
136
-    });
137
-    this.raw = this.session + this.media.join('');
138
-    return lines;
139
-};
140
-
141
-// add content's to a jingle element
142
-SDP.prototype.toJingle = function (elem, thecreator, ssrcs) {
143
-//    console.log("SSRC" + ssrcs["audio"] + " - " + ssrcs["video"]);
144
-    var i, j, k, mline, ssrc, rtpmap, tmp, lines;
145
-    // new bundle plan
146
-    if (SDPUtil.find_line(this.session, 'a=group:')) {
147
-        lines = SDPUtil.find_lines(this.session, 'a=group:');
148
-        for (i = 0; i < lines.length; i++) {
149
-            tmp = lines[i].split(' ');
150
-            var semantics = tmp.shift().substr(8);
151
-            elem.c('group', {xmlns: 'urn:xmpp:jingle:apps:grouping:0', semantics:semantics});
152
-            for (j = 0; j < tmp.length; j++) {
153
-                elem.c('content', {name: tmp[j]}).up();
154
-            }
155
-            elem.up();
156
-        }
157
-    }
158
-    for (i = 0; i < this.media.length; i++) {
159
-        mline = SDPUtil.parse_mline(this.media[i].split('\r\n')[0]);
160
-        if (!(mline.media === 'audio' ||
161
-              mline.media === 'video' ||
162
-              mline.media === 'application'))
163
-        {
164
-            continue;
165
-        }
166
-        if (SDPUtil.find_line(this.media[i], 'a=ssrc:')) {
167
-            ssrc = SDPUtil.find_line(this.media[i], 'a=ssrc:').substring(7).split(' ')[0]; // take the first
168
-        } else {
169
-            if(ssrcs && ssrcs[mline.media]) {
170
-                ssrc = ssrcs[mline.media];
171
-            } else {
172
-                ssrc = false;
173
-            }
174
-        }
175
-
176
-        elem.c('content', {creator: thecreator, name: mline.media});
177
-        if (SDPUtil.find_line(this.media[i], 'a=mid:')) {
178
-            // prefer identifier from a=mid if present
179
-            var mid = SDPUtil.parse_mid(SDPUtil.find_line(this.media[i], 'a=mid:'));
180
-            elem.attrs({ name: mid });
181
-        }
182
-
183
-        if (SDPUtil.find_line(this.media[i], 'a=rtpmap:').length) {
184
-            elem.c('description',
185
-                {xmlns: 'urn:xmpp:jingle:apps:rtp:1',
186
-                    media: mline.media });
187
-            if (ssrc) {
188
-                elem.attrs({ssrc: ssrc});
189
-            }
190
-            for (j = 0; j < mline.fmt.length; j++) {
191
-                rtpmap = SDPUtil.find_line(this.media[i], 'a=rtpmap:' + mline.fmt[j]);
192
-                elem.c('payload-type', SDPUtil.parse_rtpmap(rtpmap));
193
-                // put any 'a=fmtp:' + mline.fmt[j] lines into <param name=foo value=bar/>
194
-                if (SDPUtil.find_line(this.media[i], 'a=fmtp:' + mline.fmt[j])) {
195
-                    tmp = SDPUtil.parse_fmtp(SDPUtil.find_line(this.media[i], 'a=fmtp:' + mline.fmt[j]));
196
-                    for (k = 0; k < tmp.length; k++) {
197
-                        elem.c('parameter', tmp[k]).up();
198
-                    }
199
-                }
200
-                this.rtcpFbToJingle(i, elem, mline.fmt[j]); // XEP-0293 -- map a=rtcp-fb
201
-
202
-                elem.up();
203
-            }
204
-            if (SDPUtil.find_line(this.media[i], 'a=crypto:', this.session)) {
205
-                elem.c('encryption', {required: 1});
206
-                var crypto = SDPUtil.find_lines(this.media[i], 'a=crypto:', this.session);
207
-                crypto.forEach(function(line) {
208
-                    elem.c('crypto', SDPUtil.parse_crypto(line)).up();
209
-                });
210
-                elem.up(); // end of encryption
211
-            }
212
-
213
-            if (ssrc) {
214
-                // new style mapping
215
-                elem.c('source', { ssrc: ssrc, xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
216
-                // FIXME: group by ssrc and support multiple different ssrcs
217
-                var ssrclines = SDPUtil.find_lines(this.media[i], 'a=ssrc:');
218
-                if(ssrclines.length > 0) {
219
-                    ssrclines.forEach(function (line) {
220
-                        var idx = line.indexOf(' ');
221
-                        var linessrc = line.substr(0, idx).substr(7);
222
-                        if (linessrc != ssrc) {
223
-                            elem.up();
224
-                            ssrc = linessrc;
225
-                            elem.c('source', { ssrc: ssrc, xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
226
-                        }
227
-                        var kv = line.substr(idx + 1);
228
-                        elem.c('parameter');
229
-                        if (kv.indexOf(':') == -1) {
230
-                            elem.attrs({ name: kv });
231
-                        } else {
232
-                            var k = kv.split(':', 2)[0];
233
-                            elem.attrs({ name: k });
234
-
235
-                            var v = kv.split(':', 2)[1];
236
-                            v = SDPUtil.filter_special_chars(v);
237
-                            elem.attrs({ value: v });
238
-                        }
239
-                        elem.up();
240
-                    });
241
-                } else {
242
-                    elem.up();
243
-                    elem.c('source', { ssrc: ssrc, xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
244
-                    elem.c('parameter');
245
-                    elem.attrs({name: "cname", value:Math.random().toString(36).substring(7)});
246
-                    elem.up();
247
-                    var msid = null;
248
-                    if(mline.media == "audio") {
249
-                        msid = APP.RTC.localAudio.getId();
250
-                    } else {
251
-                        msid = APP.RTC.localVideo.getId();
252
-                    }
253
-                    if(msid !== null) {
254
-                        msid = SDPUtil.filter_special_chars(msid);
255
-                        elem.c('parameter');
256
-                        elem.attrs({name: "msid", value:msid});
257
-                        elem.up();
258
-                        elem.c('parameter');
259
-                        elem.attrs({name: "mslabel", value:msid});
260
-                        elem.up();
261
-                        elem.c('parameter');
262
-                        elem.attrs({name: "label", value:msid});
263
-                        elem.up();
264
-                    }
265
-                }
266
-                elem.up();
267
-
268
-                // XEP-0339 handle ssrc-group attributes
269
-                var ssrc_group_lines = SDPUtil.find_lines(this.media[i], 'a=ssrc-group:');
270
-                ssrc_group_lines.forEach(function(line) {
271
-                    var idx = line.indexOf(' ');
272
-                    var semantics = line.substr(0, idx).substr(13);
273
-                    var ssrcs = line.substr(14 + semantics.length).split(' ');
274
-                    if (ssrcs.length) {
275
-                        elem.c('ssrc-group', { semantics: semantics, xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
276
-                        ssrcs.forEach(function(ssrc) {
277
-                            elem.c('source', { ssrc: ssrc })
278
-                                .up();
279
-                        });
280
-                        elem.up();
281
-                    }
282
-                });
283
-            }
284
-
285
-            if (SDPUtil.find_line(this.media[i], 'a=rtcp-mux')) {
286
-                elem.c('rtcp-mux').up();
287
-            }
288
-
289
-            // XEP-0293 -- map a=rtcp-fb:*
290
-            this.rtcpFbToJingle(i, elem, '*');
291
-
292
-            // XEP-0294
293
-            if (SDPUtil.find_line(this.media[i], 'a=extmap:')) {
294
-                lines = SDPUtil.find_lines(this.media[i], 'a=extmap:');
295
-                for (j = 0; j < lines.length; j++) {
296
-                    tmp = SDPUtil.parse_extmap(lines[j]);
297
-                    elem.c('rtp-hdrext', { xmlns: 'urn:xmpp:jingle:apps:rtp:rtp-hdrext:0',
298
-                        uri: tmp.uri,
299
-                        id: tmp.value });
300
-                    if (tmp.hasOwnProperty('direction')) {
301
-                        switch (tmp.direction) {
302
-                            case 'sendonly':
303
-                                elem.attrs({senders: 'responder'});
304
-                                break;
305
-                            case 'recvonly':
306
-                                elem.attrs({senders: 'initiator'});
307
-                                break;
308
-                            case 'sendrecv':
309
-                                elem.attrs({senders: 'both'});
310
-                                break;
311
-                            case 'inactive':
312
-                                elem.attrs({senders: 'none'});
313
-                                break;
314
-                        }
315
-                    }
316
-                    // TODO: handle params
317
-                    elem.up();
318
-                }
319
-            }
320
-            elem.up(); // end of description
321
-        }
322
-
323
-        // map ice-ufrag/pwd, dtls fingerprint, candidates
324
-        this.transportToJingle(i, elem);
325
-
326
-        if (SDPUtil.find_line(this.media[i], 'a=sendrecv', this.session)) {
327
-            elem.attrs({senders: 'both'});
328
-        } else if (SDPUtil.find_line(this.media[i], 'a=sendonly', this.session)) {
329
-            elem.attrs({senders: 'initiator'});
330
-        } else if (SDPUtil.find_line(this.media[i], 'a=recvonly', this.session)) {
331
-            elem.attrs({senders: 'responder'});
332
-        } else if (SDPUtil.find_line(this.media[i], 'a=inactive', this.session)) {
333
-            elem.attrs({senders: 'none'});
334
-        }
335
-        if (mline.port == '0') {
336
-            // estos hack to reject an m-line
337
-            elem.attrs({senders: 'rejected'});
338
-        }
339
-        elem.up(); // end of content
340
-    }
341
-    elem.up();
342
-    return elem;
343
-};
344
-
345
-SDP.prototype.transportToJingle = function (mediaindex, elem) {
346
-    var tmp, sctpmap, sctpAttrs, fingerprints;
347
-    var self = this;
348
-    elem.c('transport');
349
-
350
-    // XEP-0343 DTLS/SCTP
351
-    if (SDPUtil.find_line(this.media[mediaindex], 'a=sctpmap:').length)
352
-    {
353
-        sctpmap = SDPUtil.find_line(
354
-            this.media[mediaindex], 'a=sctpmap:', self.session);
355
-        if (sctpmap)
356
-        {
357
-            sctpAttrs = SDPUtil.parse_sctpmap(sctpmap);
358
-            elem.c('sctpmap',
359
-                {
360
-                    xmlns: 'urn:xmpp:jingle:transports:dtls-sctp:1',
361
-                    number: sctpAttrs[0], /* SCTP port */
362
-                    protocol: sctpAttrs[1] /* protocol */
363
-                });
364
-            // Optional stream count attribute
365
-            if (sctpAttrs.length > 2)
366
-                elem.attrs({ streams: sctpAttrs[2]});
367
-            elem.up();
368
-        }
369
-    }
370
-    // XEP-0320
371
-    fingerprints = SDPUtil.find_lines(this.media[mediaindex], 'a=fingerprint:', this.session);
372
-    fingerprints.forEach(function(line) {
373
-        tmp = SDPUtil.parse_fingerprint(line);
374
-        tmp.xmlns = 'urn:xmpp:jingle:apps:dtls:0';
375
-        elem.c('fingerprint').t(tmp.fingerprint);
376
-        delete tmp.fingerprint;
377
-        line = SDPUtil.find_line(self.media[mediaindex], 'a=setup:', self.session);
378
-        if (line) {
379
-            tmp.setup = line.substr(8);
380
-        }
381
-        elem.attrs(tmp);
382
-        elem.up(); // end of fingerprint
383
-    });
384
-    tmp = SDPUtil.iceparams(this.media[mediaindex], this.session);
385
-    if (tmp) {
386
-        tmp.xmlns = 'urn:xmpp:jingle:transports:ice-udp:1';
387
-        elem.attrs(tmp);
388
-        // XEP-0176
389
-        if (SDPUtil.find_line(this.media[mediaindex], 'a=candidate:', this.session)) { // add any a=candidate lines
390
-            var lines = SDPUtil.find_lines(this.media[mediaindex], 'a=candidate:', this.session);
391
-            lines.forEach(function (line) {
392
-                var candidate = SDPUtil.candidateToJingle(line);
393
-                var protocol = (candidate &&
394
-                        typeof candidate.protocol === 'string')
395
-                    ? candidate.protocol.toLowerCase() : '';
396
-                if ((self.removeTcpCandidates && protocol === 'tcp') ||
397
-                    (self.removeUdpCandidates && protocol === 'udp')) {
398
-                    return;
399
-                }
400
-                elem.c('candidate', candidate).up();
401
-            });
402
-        }
403
-    }
404
-    elem.up(); // end of transport
405
-};
406
-
407
-SDP.prototype.rtcpFbToJingle = function (mediaindex, elem, payloadtype) { // XEP-0293
408
-    var lines = SDPUtil.find_lines(this.media[mediaindex], 'a=rtcp-fb:' + payloadtype);
409
-    lines.forEach(function (line) {
410
-        var tmp = SDPUtil.parse_rtcpfb(line);
411
-        if (tmp.type == 'trr-int') {
412
-            elem.c('rtcp-fb-trr-int', {xmlns: 'urn:xmpp:jingle:apps:rtp:rtcp-fb:0', value: tmp.params[0]});
413
-            elem.up();
414
-        } else {
415
-            elem.c('rtcp-fb', {xmlns: 'urn:xmpp:jingle:apps:rtp:rtcp-fb:0', type: tmp.type});
416
-            if (tmp.params.length > 0) {
417
-                elem.attrs({'subtype': tmp.params[0]});
418
-            }
419
-            elem.up();
420
-        }
421
-    });
422
-};
423
-
424
-SDP.prototype.rtcpFbFromJingle = function (elem, payloadtype) { // XEP-0293
425
-    var media = '';
426
-    var tmp = elem.find('>rtcp-fb-trr-int[xmlns="urn:xmpp:jingle:apps:rtp:rtcp-fb:0"]');
427
-    if (tmp.length) {
428
-        media += 'a=rtcp-fb:' + '*' + ' ' + 'trr-int' + ' ';
429
-        if (tmp.attr('value')) {
430
-            media += tmp.attr('value');
431
-        } else {
432
-            media += '0';
433
-        }
434
-        media += '\r\n';
435
-    }
436
-    tmp = elem.find('>rtcp-fb[xmlns="urn:xmpp:jingle:apps:rtp:rtcp-fb:0"]');
437
-    tmp.each(function () {
438
-        media += 'a=rtcp-fb:' + payloadtype + ' ' + $(this).attr('type');
439
-        if ($(this).attr('subtype')) {
440
-            media += ' ' + $(this).attr('subtype');
441
-        }
442
-        media += '\r\n';
443
-    });
444
-    return media;
445
-};
446
-
447
-// construct an SDP from a jingle stanza
448
-SDP.prototype.fromJingle = function (jingle) {
449
-    var self = this;
450
-    this.raw = 'v=0\r\n' +
451
-        'o=- 1923518516 2 IN IP4 0.0.0.0\r\n' +// FIXME
452
-        's=-\r\n' +
453
-        't=0 0\r\n';
454
-    // http://tools.ietf.org/html/draft-ietf-mmusic-sdp-bundle-negotiation-04#section-8
455
-    if ($(jingle).find('>group[xmlns="urn:xmpp:jingle:apps:grouping:0"]').length) {
456
-        $(jingle).find('>group[xmlns="urn:xmpp:jingle:apps:grouping:0"]').each(function (idx, group) {
457
-            var contents = $(group).find('>content').map(function (idx, content) {
458
-                return content.getAttribute('name');
459
-            }).get();
460
-            if (contents.length > 0) {
461
-                self.raw += 'a=group:' + (group.getAttribute('semantics') || group.getAttribute('type')) + ' ' + contents.join(' ') + '\r\n';
462
-            }
463
-        });
464
-    }
465
-
466
-    this.session = this.raw;
467
-    jingle.find('>content').each(function () {
468
-        var m = self.jingle2media($(this));
469
-        self.media.push(m);
470
-    });
471
-
472
-    // reconstruct msid-semantic -- apparently not necessary
473
-    /*
474
-     var msid = SDPUtil.parse_ssrc(this.raw);
475
-     if (msid.hasOwnProperty('mslabel')) {
476
-     this.session += "a=msid-semantic: WMS " + msid.mslabel + "\r\n";
477
-     }
478
-     */
479
-
480
-    this.raw = this.session + this.media.join('');
481
-};
482
-
483
-// translate a jingle content element into an an SDP media part
484
-SDP.prototype.jingle2media = function (content) {
485
-    var media = '',
486
-        desc = content.find('description'),
487
-        ssrc = desc.attr('ssrc'),
488
-        self = this,
489
-        tmp;
490
-    var sctp = content.find(
491
-        '>transport>sctpmap[xmlns="urn:xmpp:jingle:transports:dtls-sctp:1"]');
492
-
493
-    tmp = { media: desc.attr('media') };
494
-    tmp.port = '1';
495
-    if (content.attr('senders') == 'rejected') {
496
-        // estos hack to reject an m-line.
497
-        tmp.port = '0';
498
-    }
499
-    if (content.find('>transport>fingerprint').length || desc.find('encryption').length) {
500
-        if (sctp.length)
501
-            tmp.proto = 'DTLS/SCTP';
502
-        else
503
-            tmp.proto = 'RTP/SAVPF';
504
-    } else {
505
-        tmp.proto = 'RTP/AVPF';
506
-    }
507
-    if (!sctp.length) {
508
-        tmp.fmt = desc.find('payload-type').map(
509
-            function () { return this.getAttribute('id'); }).get();
510
-        media += SDPUtil.build_mline(tmp) + '\r\n';
511
-    } else {
512
-        media += 'm=application 1 DTLS/SCTP ' + sctp.attr('number') + '\r\n';
513
-        media += 'a=sctpmap:' + sctp.attr('number') +
514
-            ' ' + sctp.attr('protocol');
515
-
516
-        var streamCount = sctp.attr('streams');
517
-        if (streamCount)
518
-            media += ' ' + streamCount + '\r\n';
519
-        else
520
-            media += '\r\n';
521
-    }
522
-
523
-    media += 'c=IN IP4 0.0.0.0\r\n';
524
-    if (!sctp.length)
525
-        media += 'a=rtcp:1 IN IP4 0.0.0.0\r\n';
526
-    tmp = content.find('>transport[xmlns="urn:xmpp:jingle:transports:ice-udp:1"]');
527
-    if (tmp.length) {
528
-        if (tmp.attr('ufrag')) {
529
-            media += SDPUtil.build_iceufrag(tmp.attr('ufrag')) + '\r\n';
530
-        }
531
-        if (tmp.attr('pwd')) {
532
-            media += SDPUtil.build_icepwd(tmp.attr('pwd')) + '\r\n';
533
-        }
534
-        tmp.find('>fingerprint').each(function () {
535
-            // FIXME: check namespace at some point
536
-            media += 'a=fingerprint:' + this.getAttribute('hash');
537
-            media += ' ' + $(this).text();
538
-            media += '\r\n';
539
-            if (this.getAttribute('setup')) {
540
-                media += 'a=setup:' + this.getAttribute('setup') + '\r\n';
541
-            }
542
-        });
543
-    }
544
-    switch (content.attr('senders')) {
545
-        case 'initiator':
546
-            media += 'a=sendonly\r\n';
547
-            break;
548
-        case 'responder':
549
-            media += 'a=recvonly\r\n';
550
-            break;
551
-        case 'none':
552
-            media += 'a=inactive\r\n';
553
-            break;
554
-        case 'both':
555
-            media += 'a=sendrecv\r\n';
556
-            break;
557
-    }
558
-    media += 'a=mid:' + content.attr('name') + '\r\n';
559
-
560
-    // <description><rtcp-mux/></description>
561
-    // see http://code.google.com/p/libjingle/issues/detail?id=309 -- no spec though
562
-    // and http://mail.jabber.org/pipermail/jingle/2011-December/001761.html
563
-    if (desc.find('rtcp-mux').length) {
564
-        media += 'a=rtcp-mux\r\n';
565
-    }
566
-
567
-    if (desc.find('encryption').length) {
568
-        desc.find('encryption>crypto').each(function () {
569
-            media += 'a=crypto:' + this.getAttribute('tag');
570
-            media += ' ' + this.getAttribute('crypto-suite');
571
-            media += ' ' + this.getAttribute('key-params');
572
-            if (this.getAttribute('session-params')) {
573
-                media += ' ' + this.getAttribute('session-params');
574
-            }
575
-            media += '\r\n';
576
-        });
577
-    }
578
-    desc.find('payload-type').each(function () {
579
-        media += SDPUtil.build_rtpmap(this) + '\r\n';
580
-        if ($(this).find('>parameter').length) {
581
-            media += 'a=fmtp:' + this.getAttribute('id') + ' ';
582
-            media += $(this).find('parameter').map(function () {
583
-                return (this.getAttribute('name')
584
-                        ? (this.getAttribute('name') + '=') : '') +
585
-                    this.getAttribute('value');
586
-            }).get().join('; ');
587
-            media += '\r\n';
588
-        }
589
-        // xep-0293
590
-        media += self.rtcpFbFromJingle($(this), this.getAttribute('id'));
591
-    });
592
-
593
-    // xep-0293
594
-    media += self.rtcpFbFromJingle(desc, '*');
595
-
596
-    // xep-0294
597
-    tmp = desc.find('>rtp-hdrext[xmlns="urn:xmpp:jingle:apps:rtp:rtp-hdrext:0"]');
598
-    tmp.each(function () {
599
-        media += 'a=extmap:' + this.getAttribute('id') + ' ' + this.getAttribute('uri') + '\r\n';
600
-    });
601
-
602
-    content.find('>transport[xmlns="urn:xmpp:jingle:transports:ice-udp:1"]>candidate').each(function () {
603
-        var protocol = this.getAttribute('protocol');
604
-        protocol = (typeof protocol === 'string') ? protocol.toLowerCase(): '';
605
-
606
-        if ((self.removeTcpCandidates && protocol === 'tcp') ||
607
-            (self.removeUdpCandidates && protocol === 'udp')) {
608
-            return;
609
-        }
610
-
611
-        media += SDPUtil.candidateFromJingle(this);
612
-    });
613
-
614
-    // XEP-0339 handle ssrc-group attributes
615
-    content.find('description>ssrc-group[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]').each(function() {
616
-        var semantics = this.getAttribute('semantics');
617
-        var ssrcs = $(this).find('>source').map(function() {
618
-            return this.getAttribute('ssrc');
619
-        }).get();
620
-
621
-        if (ssrcs.length) {
622
-            media += 'a=ssrc-group:' + semantics + ' ' + ssrcs.join(' ') + '\r\n';
623
-        }
624
-    });
625
-
626
-    tmp = content.find('description>source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]');
627
-    tmp.each(function () {
628
-        var ssrc = this.getAttribute('ssrc');
629
-        $(this).find('>parameter').each(function () {
630
-            var name = this.getAttribute('name');
631
-            var value = this.getAttribute('value');
632
-            value = SDPUtil.filter_special_chars(value);
633
-            media += 'a=ssrc:' + ssrc + ' ' + name;
634
-            if (value && value.length)
635
-                media += ':' + value;
636
-            media += '\r\n';
637
-        });
638
-    });
639
-
640
-    return media;
641
-};
642
-
643
-
644
-module.exports = SDP;
645
-

+ 0
- 168
modules/xmpp/SDPDiffer.js ファイルの表示

@@ -1,168 +0,0 @@
1
-var SDPUtil = require("./SDPUtil");
2
-
3
-function SDPDiffer(mySDP, otherSDP)
4
-{
5
-    this.mySDP = mySDP;
6
-    this.otherSDP = otherSDP;
7
-}
8
-
9
-/**
10
- * Returns map of MediaChannel that contains media contained in
11
- * 'mySDP', but not contained in 'otherSdp'. Mapped by channel idx.
12
- */
13
-SDPDiffer.prototype.getNewMedia = function() {
14
-
15
-    // this could be useful in Array.prototype.
16
-    function arrayEquals(array) {
17
-        // if the other array is a falsy value, return
18
-        if (!array)
19
-            return false;
20
-
21
-        // compare lengths - can save a lot of time
22
-        if (this.length != array.length)
23
-            return false;
24
-
25
-        for (var i = 0, l=this.length; i < l; i++) {
26
-            // Check if we have nested arrays
27
-            if (this[i] instanceof Array && array[i] instanceof Array) {
28
-                // recurse into the nested arrays
29
-                if (!this[i].equals(array[i]))
30
-                    return false;
31
-            }
32
-            else if (this[i] != array[i]) {
33
-                // Warning - two different object instances will never be
34
-                // equal: {x:20} != {x:20}
35
-                return false;
36
-            }
37
-        }
38
-        return true;
39
-    }
40
-
41
-    var myMedias = this.mySDP.getMediaSsrcMap();
42
-    var othersMedias = this.otherSDP.getMediaSsrcMap();
43
-    var newMedia = {};
44
-    Object.keys(othersMedias).forEach(function(othersMediaIdx) {
45
-        var myMedia = myMedias[othersMediaIdx];
46
-        var othersMedia = othersMedias[othersMediaIdx];
47
-        if(!myMedia && othersMedia) {
48
-            // Add whole channel
49
-            newMedia[othersMediaIdx] = othersMedia;
50
-            return;
51
-        }
52
-        // Look for new ssrcs across the channel
53
-        Object.keys(othersMedia.ssrcs).forEach(function(ssrc) {
54
-            if(Object.keys(myMedia.ssrcs).indexOf(ssrc) === -1) {
55
-                // Allocate channel if we've found ssrc that doesn't exist in
56
-                // our channel
57
-                if(!newMedia[othersMediaIdx]){
58
-                    newMedia[othersMediaIdx] = {
59
-                        mediaindex: othersMedia.mediaindex,
60
-                        mid: othersMedia.mid,
61
-                        ssrcs: {},
62
-                        ssrcGroups: []
63
-                    };
64
-                }
65
-                newMedia[othersMediaIdx].ssrcs[ssrc] = othersMedia.ssrcs[ssrc];
66
-            }
67
-        });
68
-
69
-        // Look for new ssrc groups across the channels
70
-        othersMedia.ssrcGroups.forEach(function(otherSsrcGroup){
71
-
72
-            // try to match the other ssrc-group with an ssrc-group of ours
73
-            var matched = false;
74
-            for (var i = 0; i < myMedia.ssrcGroups.length; i++) {
75
-                var mySsrcGroup = myMedia.ssrcGroups[i];
76
-                if (otherSsrcGroup.semantics == mySsrcGroup.semantics &&
77
-                    arrayEquals.apply(otherSsrcGroup.ssrcs,
78
-                                      [mySsrcGroup.ssrcs])) {
79
-
80
-                    matched = true;
81
-                    break;
82
-                }
83
-            }
84
-
85
-            if (!matched) {
86
-                // Allocate channel if we've found an ssrc-group that doesn't
87
-                // exist in our channel
88
-
89
-                if(!newMedia[othersMediaIdx]){
90
-                    newMedia[othersMediaIdx] = {
91
-                        mediaindex: othersMedia.mediaindex,
92
-                        mid: othersMedia.mid,
93
-                        ssrcs: {},
94
-                        ssrcGroups: []
95
-                    };
96
-                }
97
-                newMedia[othersMediaIdx].ssrcGroups.push(otherSsrcGroup);
98
-            }
99
-        });
100
-    });
101
-    return newMedia;
102
-};
103
-
104
-/**
105
- * TODO: document!
106
- */
107
-SDPDiffer.prototype.toJingle = function(modify) {
108
-    var sdpMediaSsrcs = this.getNewMedia();
109
-
110
-    var modified = false;
111
-    Object.keys(sdpMediaSsrcs).forEach(function(mediaindex){
112
-        modified = true;
113
-        var media = sdpMediaSsrcs[mediaindex];
114
-        modify.c('content', {name: media.mid});
115
-
116
-        modify.c('description',
117
-                 {xmlns:'urn:xmpp:jingle:apps:rtp:1', media: media.mid});
118
-        // FIXME: not completely sure this operates on blocks and / or handles
119
-        // different ssrcs correctly
120
-        // generate sources from lines
121
-        Object.keys(media.ssrcs).forEach(function(ssrcNum) {
122
-            var mediaSsrc = media.ssrcs[ssrcNum];
123
-            modify.c('source', { xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
124
-            modify.attrs({ssrc: mediaSsrc.ssrc});
125
-            // iterate over ssrc lines
126
-            mediaSsrc.lines.forEach(function (line) {
127
-                var idx = line.indexOf(' ');
128
-                var kv = line.substr(idx + 1);
129
-                modify.c('parameter');
130
-                if (kv.indexOf(':') == -1) {
131
-                    modify.attrs({ name: kv });
132
-                } else {
133
-                    var nv = kv.split(':', 2);
134
-                    var name = nv[0];
135
-                    var value = SDPUtil.filter_special_chars(nv[1]);
136
-                    modify.attrs({ name: name });
137
-                    modify.attrs({ value: value });
138
-                }
139
-                modify.up(); // end of parameter
140
-            });
141
-            modify.up(); // end of source
142
-        });
143
-
144
-        // generate source groups from lines
145
-        media.ssrcGroups.forEach(function(ssrcGroup) {
146
-            if (ssrcGroup.ssrcs.length) {
147
-
148
-                modify.c('ssrc-group', {
149
-                    semantics: ssrcGroup.semantics,
150
-                    xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0'
151
-                });
152
-
153
-                ssrcGroup.ssrcs.forEach(function (ssrc) {
154
-                    modify.c('source', { ssrc: ssrc })
155
-                        .up(); // end of source
156
-                });
157
-                modify.up(); // end of ssrc-group
158
-            }
159
-        });
160
-
161
-        modify.up(); // end of description
162
-        modify.up(); // end of content
163
-    });
164
-
165
-    return modified;
166
-};
167
-
168
-module.exports = SDPDiffer;

+ 0
- 361
modules/xmpp/SDPUtil.js ファイルの表示

@@ -1,361 +0,0 @@
1
-/* jshint -W101 */
2
-var RTCBrowserType = require('../RTC/RTCBrowserType');
3
-
4
-var SDPUtil = {
5
-    filter_special_chars: function (text) {
6
-        return text.replace(/[\\\/\{,\}\+]/g, "");
7
-    },
8
-    iceparams: function (mediadesc, sessiondesc) {
9
-        var data = null;
10
-        if (SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc) &&
11
-            SDPUtil.find_line(mediadesc, 'a=ice-pwd:', sessiondesc)) {
12
-            data = {
13
-                ufrag: SDPUtil.parse_iceufrag(SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc)),
14
-                pwd: SDPUtil.parse_icepwd(SDPUtil.find_line(mediadesc, 'a=ice-pwd:', sessiondesc))
15
-            };
16
-        }
17
-        return data;
18
-    },
19
-    parse_iceufrag: function (line) {
20
-        return line.substring(12);
21
-    },
22
-    build_iceufrag: function (frag) {
23
-        return 'a=ice-ufrag:' + frag;
24
-    },
25
-    parse_icepwd: function (line) {
26
-        return line.substring(10);
27
-    },
28
-    build_icepwd: function (pwd) {
29
-        return 'a=ice-pwd:' + pwd;
30
-    },
31
-    parse_mid: function (line) {
32
-        return line.substring(6);
33
-    },
34
-    parse_mline: function (line) {
35
-        var parts = line.substring(2).split(' '),
36
-            data = {};
37
-        data.media = parts.shift();
38
-        data.port = parts.shift();
39
-        data.proto = parts.shift();
40
-        if (parts[parts.length - 1] === '') { // trailing whitespace
41
-            parts.pop();
42
-        }
43
-        data.fmt = parts;
44
-        return data;
45
-    },
46
-    build_mline: function (mline) {
47
-        return 'm=' + mline.media + ' ' + mline.port + ' ' + mline.proto + ' ' + mline.fmt.join(' ');
48
-    },
49
-    parse_rtpmap: function (line) {
50
-        var parts = line.substring(9).split(' '),
51
-            data = {};
52
-        data.id = parts.shift();
53
-        parts = parts[0].split('/');
54
-        data.name = parts.shift();
55
-        data.clockrate = parts.shift();
56
-        data.channels = parts.length ? parts.shift() : '1';
57
-        return data;
58
-    },
59
-    /**
60
-     * Parses SDP line "a=sctpmap:..." and extracts SCTP port from it.
61
-     * @param line eg. "a=sctpmap:5000 webrtc-datachannel"
62
-     * @returns [SCTP port number, protocol, streams]
63
-     */
64
-    parse_sctpmap: function (line)
65
-    {
66
-        var parts = line.substring(10).split(' ');
67
-        var sctpPort = parts[0];
68
-        var protocol = parts[1];
69
-        // Stream count is optional
70
-        var streamCount = parts.length > 2 ? parts[2] : null;
71
-        return [sctpPort, protocol, streamCount];// SCTP port
72
-    },
73
-    build_rtpmap: function (el) {
74
-        var line = 'a=rtpmap:' + el.getAttribute('id') + ' ' + el.getAttribute('name') + '/' + el.getAttribute('clockrate');
75
-        if (el.getAttribute('channels') && el.getAttribute('channels') != '1') {
76
-            line += '/' + el.getAttribute('channels');
77
-        }
78
-        return line;
79
-    },
80
-    parse_crypto: function (line) {
81
-        var parts = line.substring(9).split(' '),
82
-            data = {};
83
-        data.tag = parts.shift();
84
-        data['crypto-suite'] = parts.shift();
85
-        data['key-params'] = parts.shift();
86
-        if (parts.length) {
87
-            data['session-params'] = parts.join(' ');
88
-        }
89
-        return data;
90
-    },
91
-    parse_fingerprint: function (line) { // RFC 4572
92
-        var parts = line.substring(14).split(' '),
93
-            data = {};
94
-        data.hash = parts.shift();
95
-        data.fingerprint = parts.shift();
96
-        // TODO assert that fingerprint satisfies 2UHEX *(":" 2UHEX) ?
97
-        return data;
98
-    },
99
-    parse_fmtp: function (line) {
100
-        var parts = line.split(' '),
101
-            i, key, value,
102
-            data = [];
103
-        parts.shift();
104
-        parts = parts.join(' ').split(';');
105
-        for (i = 0; i < parts.length; i++) {
106
-            key = parts[i].split('=')[0];
107
-            while (key.length && key[0] == ' ') {
108
-                key = key.substring(1);
109
-            }
110
-            value = parts[i].split('=')[1];
111
-            if (key && value) {
112
-                data.push({name: key, value: value});
113
-            } else if (key) {
114
-                // rfc 4733 (DTMF) style stuff
115
-                data.push({name: '', value: key});
116
-            }
117
-        }
118
-        return data;
119
-    },
120
-    parse_icecandidate: function (line) {
121
-        var candidate = {},
122
-            elems = line.split(' ');
123
-        candidate.foundation = elems[0].substring(12);
124
-        candidate.component = elems[1];
125
-        candidate.protocol = elems[2].toLowerCase();
126
-        candidate.priority = elems[3];
127
-        candidate.ip = elems[4];
128
-        candidate.port = elems[5];
129
-        // elems[6] => "typ"
130
-        candidate.type = elems[7];
131
-        candidate.generation = 0; // default value, may be overwritten below
132
-        for (var i = 8; i < elems.length; i += 2) {
133
-            switch (elems[i]) {
134
-                case 'raddr':
135
-                    candidate['rel-addr'] = elems[i + 1];
136
-                    break;
137
-                case 'rport':
138
-                    candidate['rel-port'] = elems[i + 1];
139
-                    break;
140
-                case 'generation':
141
-                    candidate.generation = elems[i + 1];
142
-                    break;
143
-                case 'tcptype':
144
-                    candidate.tcptype = elems[i + 1];
145
-                    break;
146
-                default: // TODO
147
-                    console.log('parse_icecandidate not translating "' + elems[i] + '" = "' + elems[i + 1] + '"');
148
-            }
149
-        }
150
-        candidate.network = '1';
151
-        candidate.id = Math.random().toString(36).substr(2, 10); // not applicable to SDP -- FIXME: should be unique, not just random
152
-        return candidate;
153
-    },
154
-    build_icecandidate: function (cand) {
155
-        var line = ['a=candidate:' + cand.foundation, cand.component, cand.protocol, cand.priority, cand.ip, cand.port, 'typ', cand.type].join(' ');
156
-        line += ' ';
157
-        switch (cand.type) {
158
-            case 'srflx':
159
-            case 'prflx':
160
-            case 'relay':
161
-                if (cand.hasOwnAttribute('rel-addr') && cand.hasOwnAttribute('rel-port')) {
162
-                    line += 'raddr';
163
-                    line += ' ';
164
-                    line += cand['rel-addr'];
165
-                    line += ' ';
166
-                    line += 'rport';
167
-                    line += ' ';
168
-                    line += cand['rel-port'];
169
-                    line += ' ';
170
-                }
171
-                break;
172
-        }
173
-        if (cand.hasOwnAttribute('tcptype')) {
174
-            line += 'tcptype';
175
-            line += ' ';
176
-            line += cand.tcptype;
177
-            line += ' ';
178
-        }
179
-        line += 'generation';
180
-        line += ' ';
181
-        line += cand.hasOwnAttribute('generation') ? cand.generation : '0';
182
-        return line;
183
-    },
184
-    parse_ssrc: function (desc) {
185
-        // proprietary mapping of a=ssrc lines
186
-        // TODO: see "Jingle RTP Source Description" by Juberti and P. Thatcher on google docs
187
-        // and parse according to that
188
-        var lines = desc.split('\r\n'),
189
-            data = {};
190
-        for (var i = 0; i < lines.length; i++) {
191
-            if (lines[i].substring(0, 7) == 'a=ssrc:') {
192
-                var idx = lines[i].indexOf(' ');
193
-                data[lines[i].substr(idx + 1).split(':', 2)[0]] = lines[i].substr(idx + 1).split(':', 2)[1];
194
-            }
195
-        }
196
-        return data;
197
-    },
198
-    parse_rtcpfb: function (line) {
199
-        var parts = line.substr(10).split(' ');
200
-        var data = {};
201
-        data.pt = parts.shift();
202
-        data.type = parts.shift();
203
-        data.params = parts;
204
-        return data;
205
-    },
206
-    parse_extmap: function (line) {
207
-        var parts = line.substr(9).split(' ');
208
-        var data = {};
209
-        data.value = parts.shift();
210
-        if (data.value.indexOf('/') != -1) {
211
-            data.direction = data.value.substr(data.value.indexOf('/') + 1);
212
-            data.value = data.value.substr(0, data.value.indexOf('/'));
213
-        } else {
214
-            data.direction = 'both';
215
-        }
216
-        data.uri = parts.shift();
217
-        data.params = parts;
218
-        return data;
219
-    },
220
-    find_line: function (haystack, needle, sessionpart) {
221
-        var lines = haystack.split('\r\n');
222
-        for (var i = 0; i < lines.length; i++) {
223
-            if (lines[i].substring(0, needle.length) == needle) {
224
-                return lines[i];
225
-            }
226
-        }
227
-        if (!sessionpart) {
228
-            return false;
229
-        }
230
-        // search session part
231
-        lines = sessionpart.split('\r\n');
232
-        for (var j = 0; j < lines.length; j++) {
233
-            if (lines[j].substring(0, needle.length) == needle) {
234
-                return lines[j];
235
-            }
236
-        }
237
-        return false;
238
-    },
239
-    find_lines: function (haystack, needle, sessionpart) {
240
-        var lines = haystack.split('\r\n'),
241
-            needles = [];
242
-        for (var i = 0; i < lines.length; i++) {
243
-            if (lines[i].substring(0, needle.length) == needle)
244
-                needles.push(lines[i]);
245
-        }
246
-        if (needles.length || !sessionpart) {
247
-            return needles;
248
-        }
249
-        // search session part
250
-        lines = sessionpart.split('\r\n');
251
-        for (var j = 0; j < lines.length; j++) {
252
-            if (lines[j].substring(0, needle.length) == needle) {
253
-                needles.push(lines[j]);
254
-            }
255
-        }
256
-        return needles;
257
-    },
258
-    candidateToJingle: function (line) {
259
-        // a=candidate:2979166662 1 udp 2113937151 192.168.2.100 57698 typ host generation 0
260
-        //      <candidate component=... foundation=... generation=... id=... ip=... network=... port=... priority=... protocol=... type=.../>
261
-        if (line.indexOf('candidate:') === 0) {
262
-            line = 'a=' + line;
263
-        } else if (line.substring(0, 12) != 'a=candidate:') {
264
-            console.log('parseCandidate called with a line that is not a candidate line');
265
-            console.log(line);
266
-            return null;
267
-        }
268
-        if (line.substring(line.length - 2) == '\r\n') // chomp it
269
-            line = line.substring(0, line.length - 2);
270
-        var candidate = {},
271
-            elems = line.split(' '),
272
-            i;
273
-        if (elems[6] != 'typ') {
274
-            console.log('did not find typ in the right place');
275
-            console.log(line);
276
-            return null;
277
-        }
278
-        candidate.foundation = elems[0].substring(12);
279
-        candidate.component = elems[1];
280
-        candidate.protocol = elems[2].toLowerCase();
281
-        candidate.priority = elems[3];
282
-        candidate.ip = elems[4];
283
-        candidate.port = elems[5];
284
-        // elems[6] => "typ"
285
-        candidate.type = elems[7];
286
-
287
-        candidate.generation = '0'; // default, may be overwritten below
288
-        for (i = 8; i < elems.length; i += 2) {
289
-            switch (elems[i]) {
290
-                case 'raddr':
291
-                    candidate['rel-addr'] = elems[i + 1];
292
-                    break;
293
-                case 'rport':
294
-                    candidate['rel-port'] = elems[i + 1];
295
-                    break;
296
-                case 'generation':
297
-                    candidate.generation = elems[i + 1];
298
-                    break;
299
-                case 'tcptype':
300
-                    candidate.tcptype = elems[i + 1];
301
-                    break;
302
-                default: // TODO
303
-                    console.log('not translating "' + elems[i] + '" = "' + elems[i + 1] + '"');
304
-            }
305
-        }
306
-        candidate.network = '1';
307
-        candidate.id = Math.random().toString(36).substr(2, 10); // not applicable to SDP -- FIXME: should be unique, not just random
308
-        return candidate;
309
-    },
310
-    candidateFromJingle: function (cand) {
311
-        var line = 'a=candidate:';
312
-        line += cand.getAttribute('foundation');
313
-        line += ' ';
314
-        line += cand.getAttribute('component');
315
-        line += ' ';
316
-
317
-        var protocol = cand.getAttribute('protocol');
318
-        // use tcp candidates for FF
319
-        if (RTCBrowserType.isFirefox() && protocol.toLowerCase() == 'ssltcp') {
320
-            protocol = 'tcp';
321
-        }
322
-
323
-        line += ' ';
324
-        line += cand.getAttribute('priority');
325
-        line += ' ';
326
-        line += cand.getAttribute('ip');
327
-        line += ' ';
328
-        line += cand.getAttribute('port');
329
-        line += ' ';
330
-        line += 'typ';
331
-        line += ' ' + cand.getAttribute('type');
332
-        line += ' ';
333
-        switch (cand.getAttribute('type')) {
334
-            case 'srflx':
335
-            case 'prflx':
336
-            case 'relay':
337
-                if (cand.getAttribute('rel-addr') && cand.getAttribute('rel-port')) {
338
-                    line += 'raddr';
339
-                    line += ' ';
340
-                    line += cand.getAttribute('rel-addr');
341
-                    line += ' ';
342
-                    line += 'rport';
343
-                    line += ' ';
344
-                    line += cand.getAttribute('rel-port');
345
-                    line += ' ';
346
-                }
347
-                break;
348
-        }
349
-        if (protocol.toLowerCase() == 'tcp') {
350
-            line += 'tcptype';
351
-            line += ' ';
352
-            line += cand.getAttribute('tcptype');
353
-            line += ' ';
354
-        }
355
-        line += 'generation';
356
-        line += ' ';
357
-        line += cand.getAttribute('generation') || '0';
358
-        return line + '\r\n';
359
-    }
360
-};
361
-module.exports = SDPUtil;

+ 0
- 449
modules/xmpp/TraceablePeerConnection.js ファイルの表示

@@ -1,449 +0,0 @@
1
-/* global $, config, mozRTCPeerConnection, RTCPeerConnection,
2
-    webkitRTCPeerConnection, RTCSessionDescription */
3
-/* jshint -W101 */
4
-var RTC = require('../RTC/RTC');
5
-var RTCBrowserType = require("../RTC/RTCBrowserType");
6
-var RTCEvents = require("../../service/RTC/RTCEvents");
7
-var SSRCReplacement = require("./LocalSSRCReplacement");
8
-
9
-function TraceablePeerConnection(ice_config, constraints, session) {
10
-    var self = this;
11
-    var RTCPeerConnectionType = null;
12
-    if (RTCBrowserType.isFirefox()) {
13
-        RTCPeerConnectionType = mozRTCPeerConnection;
14
-    } else if (RTCBrowserType.isTemasysPluginUsed()) {
15
-        RTCPeerConnectionType = RTCPeerConnection;
16
-    } else {
17
-        RTCPeerConnectionType = webkitRTCPeerConnection;
18
-    }
19
-    self.eventEmitter = session.eventEmitter;
20
-    this.peerconnection = new RTCPeerConnectionType(ice_config, constraints);
21
-    this.updateLog = [];
22
-    this.stats = {};
23
-    this.statsinterval = null;
24
-    this.maxstats = 0; // limit to 300 values, i.e. 5 minutes; set to 0 to disable
25
-    var Interop = require('sdp-interop').Interop;
26
-    this.interop = new Interop();
27
-    var Simulcast = require('sdp-simulcast');
28
-    this.simulcast = new Simulcast({numOfLayers: 3, explodeRemoteSimulcast: false});
29
-
30
-    // override as desired
31
-    this.trace = function (what, info) {
32
-        /*console.warn('WTRACE', what, info);
33
-        if (info && RTCBrowserType.isIExplorer()) {
34
-            if (info.length > 1024) {
35
-                console.warn('WTRACE', what, info.substr(1024));
36
-            }
37
-            if (info.length > 2048) {
38
-                console.warn('WTRACE', what, info.substr(2048));
39
-            }
40
-        }*/
41
-        self.updateLog.push({
42
-            time: new Date(),
43
-            type: what,
44
-            value: info || ""
45
-        });
46
-    };
47
-    this.onicecandidate = null;
48
-    this.peerconnection.onicecandidate = function (event) {
49
-        // FIXME: this causes stack overflow with Temasys Plugin
50
-        if (!RTCBrowserType.isTemasysPluginUsed())
51
-            self.trace('onicecandidate', JSON.stringify(event.candidate, null, ' '));
52
-        if (self.onicecandidate !== null) {
53
-            self.onicecandidate(event);
54
-        }
55
-    };
56
-    this.onaddstream = null;
57
-    this.peerconnection.onaddstream = function (event) {
58
-        self.trace('onaddstream', event.stream.id);
59
-        if (self.onaddstream !== null) {
60
-            self.onaddstream(event);
61
-        }
62
-    };
63
-    this.onremovestream = null;
64
-    this.peerconnection.onremovestream = function (event) {
65
-        self.trace('onremovestream', event.stream.id);
66
-        if (self.onremovestream !== null) {
67
-            self.onremovestream(event);
68
-        }
69
-    };
70
-    this.onsignalingstatechange = null;
71
-    this.peerconnection.onsignalingstatechange = function (event) {
72
-        self.trace('onsignalingstatechange', self.signalingState);
73
-        if (self.onsignalingstatechange !== null) {
74
-            self.onsignalingstatechange(event);
75
-        }
76
-    };
77
-    this.oniceconnectionstatechange = null;
78
-    this.peerconnection.oniceconnectionstatechange = function (event) {
79
-        self.trace('oniceconnectionstatechange', self.iceConnectionState);
80
-        if (self.oniceconnectionstatechange !== null) {
81
-            self.oniceconnectionstatechange(event);
82
-        }
83
-    };
84
-    this.onnegotiationneeded = null;
85
-    this.peerconnection.onnegotiationneeded = function (event) {
86
-        self.trace('onnegotiationneeded');
87
-        if (self.onnegotiationneeded !== null) {
88
-            self.onnegotiationneeded(event);
89
-        }
90
-    };
91
-    self.ondatachannel = null;
92
-    this.peerconnection.ondatachannel = function (event) {
93
-        self.trace('ondatachannel', event);
94
-        if (self.ondatachannel !== null) {
95
-            self.ondatachannel(event);
96
-        }
97
-    };
98
-    // XXX: do all non-firefox browsers which we support also support this?
99
-    if (!RTCBrowserType.isFirefox() && this.maxstats) {
100
-        this.statsinterval = window.setInterval(function() {
101
-            self.peerconnection.getStats(function(stats) {
102
-                var results = stats.result();
103
-                var now = new Date();
104
-                for (var i = 0; i < results.length; ++i) {
105
-                    results[i].names().forEach(function (name) {
106
-                        var id = results[i].id + '-' + name;
107
-                        if (!self.stats[id]) {
108
-                            self.stats[id] = {
109
-                                startTime: now,
110
-                                endTime: now,
111
-                                values: [],
112
-                                times: []
113
-                            };
114
-                        }
115
-                        self.stats[id].values.push(results[i].stat(name));
116
-                        self.stats[id].times.push(now.getTime());
117
-                        if (self.stats[id].values.length > self.maxstats) {
118
-                            self.stats[id].values.shift();
119
-                            self.stats[id].times.shift();
120
-                        }
121
-                        self.stats[id].endTime = now;
122
-                    });
123
-                }
124
-            });
125
-
126
-        }, 1000);
127
-    }
128
-}
129
-
130
-/**
131
- * Returns a string representation of a SessionDescription object.
132
- */
133
-var dumpSDP = function(description) {
134
-    if (typeof description === 'undefined' || description === null) {
135
-        return '';
136
-    }
137
-
138
-    return 'type: ' + description.type + '\r\n' + description.sdp;
139
-};
140
-
141
-/**
142
- * Takes a SessionDescription object and returns a "normalized" version.
143
- * Currently it only takes care of ordering the a=ssrc lines.
144
- */
145
-var normalizePlanB = function(desc) {
146
-    if (typeof desc !== 'object' || desc === null ||
147
-        typeof desc.sdp !== 'string') {
148
-        console.warn('An empty description was passed as an argument.');
149
-        return desc;
150
-    }
151
-
152
-    var transform = require('sdp-transform');
153
-    var session = transform.parse(desc.sdp);
154
-
155
-    if (typeof session !== 'undefined' && typeof session.media !== 'undefined' &&
156
-        Array.isArray(session.media)) {
157
-        session.media.forEach(function (mLine) {
158
-
159
-            // Chrome appears to be picky about the order in which a=ssrc lines
160
-            // are listed in an m-line when rtx is enabled (and thus there are
161
-            // a=ssrc-group lines with FID semantics). Specifically if we have
162
-            // "a=ssrc-group:FID S1 S2" and the "a=ssrc:S2" lines appear before
163
-            // the "a=ssrc:S1" lines, SRD fails.
164
-            // So, put SSRC which appear as the first SSRC in an FID ssrc-group
165
-            // first.
166
-            var firstSsrcs = [];
167
-            var newSsrcLines = [];
168
-
169
-            if (typeof mLine.ssrcGroups !== 'undefined' && Array.isArray(mLine.ssrcGroups)) {
170
-                mLine.ssrcGroups.forEach(function (group) {
171
-                    if (typeof group.semantics !== 'undefined' &&
172
-                        group.semantics === 'FID') {
173
-                        if (typeof group.ssrcs !== 'undefined') {
174
-                            firstSsrcs.push(Number(group.ssrcs.split(' ')[0]));
175
-                        }
176
-                    }
177
-                });
178
-            }
179
-
180
-            if (typeof mLine.ssrcs !== 'undefined' && Array.isArray(mLine.ssrcs)) {
181
-                var i;
182
-                for (i = 0; i<mLine.ssrcs.length; i++){
183
-                    if (typeof mLine.ssrcs[i] === 'object'
184
-                        && typeof mLine.ssrcs[i].id !== 'undefined'
185
-                        && !$.inArray(mLine.ssrcs[i].id, firstSsrcs)) {
186
-                        newSsrcLines.push(mLine.ssrcs[i]);
187
-                        delete mLine.ssrcs[i];
188
-                    }
189
-                }
190
-
191
-                for (i = 0; i<mLine.ssrcs.length; i++){
192
-                    if (typeof mLine.ssrcs[i] !== 'undefined') {
193
-                        newSsrcLines.push(mLine.ssrcs[i]);
194
-                    }
195
-                }
196
-
197
-                mLine.ssrcs = newSsrcLines;
198
-            }
199
-        });
200
-    }
201
-
202
-    var resStr = transform.write(session);
203
-    return new RTCSessionDescription({
204
-        type: desc.type,
205
-        sdp: resStr
206
-    });
207
-};
208
-
209
-if (TraceablePeerConnection.prototype.__defineGetter__ !== undefined) {
210
-    TraceablePeerConnection.prototype.__defineGetter__(
211
-        'signalingState',
212
-        function() { return this.peerconnection.signalingState; });
213
-    TraceablePeerConnection.prototype.__defineGetter__(
214
-        'iceConnectionState',
215
-        function() { return this.peerconnection.iceConnectionState; });
216
-    TraceablePeerConnection.prototype.__defineGetter__(
217
-        'localDescription',
218
-        function() {
219
-            var desc = this.peerconnection.localDescription;
220
-
221
-            // FIXME this should probably be after the Unified Plan -> Plan B
222
-            // transformation.
223
-            desc = SSRCReplacement.mungeLocalVideoSSRC(desc);
224
-
225
-            this.trace('getLocalDescription::preTransform', dumpSDP(desc));
226
-
227
-            // if we're running on FF, transform to Plan B first.
228
-            if (RTCBrowserType.usesUnifiedPlan()) {
229
-                desc = this.interop.toPlanB(desc);
230
-                this.trace('getLocalDescription::postTransform (Plan B)', dumpSDP(desc));
231
-            }
232
-            return desc;
233
-        });
234
-    TraceablePeerConnection.prototype.__defineGetter__(
235
-        'remoteDescription',
236
-        function() {
237
-            var desc = this.peerconnection.remoteDescription;
238
-            this.trace('getRemoteDescription::preTransform', dumpSDP(desc));
239
-
240
-            // if we're running on FF, transform to Plan B first.
241
-            if (RTCBrowserType.usesUnifiedPlan()) {
242
-                desc = this.interop.toPlanB(desc);
243
-                this.trace('getRemoteDescription::postTransform (Plan B)', dumpSDP(desc));
244
-            }
245
-            return desc;
246
-        });
247
-}
248
-
249
-TraceablePeerConnection.prototype.addStream = function (stream) {
250
-    this.trace('addStream', stream.id);
251
-    try
252
-    {
253
-        this.peerconnection.addStream(stream);
254
-    }
255
-    catch (e)
256
-    {
257
-        console.error(e);
258
-    }
259
-};
260
-
261
-TraceablePeerConnection.prototype.removeStream = function (stream, stopStreams) {
262
-    this.trace('removeStream', stream.id);
263
-    if(stopStreams) {
264
-        RTC.stopMediaStream(stream);
265
-    }
266
-
267
-    try {
268
-        // FF doesn't support this yet.
269
-        if (this.peerconnection.removeStream)
270
-            this.peerconnection.removeStream(stream);
271
-    } catch (e) {
272
-        console.error(e);
273
-    }
274
-};
275
-
276
-TraceablePeerConnection.prototype.createDataChannel = function (label, opts) {
277
-    this.trace('createDataChannel', label, opts);
278
-    return this.peerconnection.createDataChannel(label, opts);
279
-};
280
-
281
-TraceablePeerConnection.prototype.setLocalDescription
282
-        = function (description, successCallback, failureCallback) {
283
-    this.trace('setLocalDescription::preTransform', dumpSDP(description));
284
-    // if we're running on FF, transform to Plan A first.
285
-    if (RTCBrowserType.usesUnifiedPlan()) {
286
-        description = this.interop.toUnifiedPlan(description);
287
-        this.trace('setLocalDescription::postTransform (Plan A)', dumpSDP(description));
288
-    }
289
-
290
-    var self = this;
291
-    this.peerconnection.setLocalDescription(description,
292
-        function () {
293
-            self.trace('setLocalDescriptionOnSuccess');
294
-            successCallback();
295
-        },
296
-        function (err) {
297
-            self.trace('setLocalDescriptionOnFailure', err);
298
-            self.eventEmitter.emit(RTCEvents.SET_LOCAL_DESCRIPTION_FAILED, err, self.peerconnection);
299
-            failureCallback(err);
300
-        }
301
-    );
302
-    /*
303
-     if (this.statsinterval === null && this.maxstats > 0) {
304
-     // start gathering stats
305
-     }
306
-     */
307
-};
308
-
309
-TraceablePeerConnection.prototype.setRemoteDescription
310
-        = function (description, successCallback, failureCallback) {
311
-    this.trace('setRemoteDescription::preTransform', dumpSDP(description));
312
-    // TODO the focus should squeze or explode the remote simulcast
313
-    description = this.simulcast.mungeRemoteDescription(description);
314
-    this.trace('setRemoteDescription::postTransform (simulcast)', dumpSDP(description));
315
-
316
-    // if we're running on FF, transform to Plan A first.
317
-    if (RTCBrowserType.usesUnifiedPlan()) {
318
-        description = this.interop.toUnifiedPlan(description);
319
-        this.trace('setRemoteDescription::postTransform (Plan A)', dumpSDP(description));
320
-    }
321
-
322
-    if (RTCBrowserType.usesPlanB()) {
323
-        description = normalizePlanB(description);
324
-    }
325
-
326
-    var self = this;
327
-    this.peerconnection.setRemoteDescription(description,
328
-        function () {
329
-            self.trace('setRemoteDescriptionOnSuccess');
330
-            successCallback();
331
-        },
332
-        function (err) {
333
-            self.trace('setRemoteDescriptionOnFailure', err);
334
-            self.eventEmitter.emit(RTCEvents.SET_REMOTE_DESCRIPTION_FAILED, err, self.peerconnection);
335
-            failureCallback(err);
336
-        }
337
-    );
338
-    /*
339
-     if (this.statsinterval === null && this.maxstats > 0) {
340
-     // start gathering stats
341
-     }
342
-     */
343
-};
344
-
345
-TraceablePeerConnection.prototype.close = function () {
346
-    this.trace('stop');
347
-    if (this.statsinterval !== null) {
348
-        window.clearInterval(this.statsinterval);
349
-        this.statsinterval = null;
350
-    }
351
-    this.peerconnection.close();
352
-};
353
-
354
-TraceablePeerConnection.prototype.createOffer
355
-        = function (successCallback, failureCallback, constraints) {
356
-    var self = this;
357
-    this.trace('createOffer', JSON.stringify(constraints, null, ' '));
358
-    this.peerconnection.createOffer(
359
-        function (offer) {
360
-            self.trace('createOfferOnSuccess::preTransform', dumpSDP(offer));
361
-            // NOTE this is not tested because in meet the focus generates the
362
-            // offer.
363
-
364
-            // if we're running on FF, transform to Plan B first.
365
-            if (RTCBrowserType.usesUnifiedPlan()) {
366
-                offer = self.interop.toPlanB(offer);
367
-                self.trace('createOfferOnSuccess::postTransform (Plan B)', dumpSDP(offer));
368
-            }
369
-
370
-            offer = SSRCReplacement.mungeLocalVideoSSRC(offer);
371
-
372
-            if (config.enableSimulcast && self.simulcast.isSupported()) {
373
-                offer = self.simulcast.mungeLocalDescription(offer);
374
-                self.trace('createOfferOnSuccess::postTransform (simulcast)', dumpSDP(offer));
375
-            }
376
-            successCallback(offer);
377
-        },
378
-        function(err) {
379
-            self.trace('createOfferOnFailure', err);
380
-            self.eventEmitter.emit(RTCEvents.CREATE_OFFER_FAILED, err, self.peerconnection);
381
-            failureCallback(err);
382
-        },
383
-        constraints
384
-    );
385
-};
386
-
387
-TraceablePeerConnection.prototype.createAnswer
388
-        = function (successCallback, failureCallback, constraints) {
389
-    var self = this;
390
-    this.trace('createAnswer', JSON.stringify(constraints, null, ' '));
391
-    this.peerconnection.createAnswer(
392
-        function (answer) {
393
-            self.trace('createAnswerOnSuccess::preTransform', dumpSDP(answer));
394
-            // if we're running on FF, transform to Plan A first.
395
-            if (RTCBrowserType.usesUnifiedPlan()) {
396
-                answer = self.interop.toPlanB(answer);
397
-                self.trace('createAnswerOnSuccess::postTransform (Plan B)', dumpSDP(answer));
398
-            }
399
-
400
-            // munge local video SSRC
401
-            answer = SSRCReplacement.mungeLocalVideoSSRC(answer);
402
-
403
-            if (config.enableSimulcast && self.simulcast.isSupported()) {
404
-                answer = self.simulcast.mungeLocalDescription(answer);
405
-                self.trace('createAnswerOnSuccess::postTransform (simulcast)', dumpSDP(answer));
406
-            }
407
-            successCallback(answer);
408
-        },
409
-        function(err) {
410
-            self.trace('createAnswerOnFailure', err);
411
-            self.eventEmitter.emit(RTCEvents.CREATE_ANSWER_FAILED, err, self.peerconnection);
412
-            failureCallback(err);
413
-        },
414
-        constraints
415
-    );
416
-};
417
-
418
-TraceablePeerConnection.prototype.addIceCandidate
419
-        = function (candidate, successCallback, failureCallback) {
420
-    //var self = this;
421
-    this.trace('addIceCandidate', JSON.stringify(candidate, null, ' '));
422
-    this.peerconnection.addIceCandidate(candidate);
423
-    /* maybe later
424
-     this.peerconnection.addIceCandidate(candidate,
425
-     function () {
426
-     self.trace('addIceCandidateOnSuccess');
427
-     successCallback();
428
-     },
429
-     function (err) {
430
-     self.trace('addIceCandidateOnFailure', err);
431
-     failureCallback(err);
432
-     }
433
-     );
434
-     */
435
-};
436
-
437
-TraceablePeerConnection.prototype.getStats = function(callback, errback) {
438
-    // TODO: Is this the correct way to handle Opera, Temasys?
439
-    if (RTCBrowserType.isFirefox()) {
440
-        // ignore for now...
441
-        if(!errback)
442
-            errback = function () {};
443
-        this.peerconnection.getStats(null, callback, errback);
444
-    } else {
445
-        this.peerconnection.getStats(callback);
446
-    }
447
-};
448
-
449
-module.exports = TraceablePeerConnection;

+ 0
- 434
modules/xmpp/moderator.js ファイルの表示

@@ -1,434 +0,0 @@
1
-/* global $, $iq, APP, config, messageHandler,
2
- roomName, sessionTerminated, Strophe, Util */
3
-var XMPPEvents = require("../../service/xmpp/XMPPEvents");
4
-var Settings = require("../settings/Settings");
5
-
6
-var AuthenticationEvents
7
-    = require("../../service/authentication/AuthenticationEvents");
8
-
9
-/**
10
- * Contains logic responsible for enabling/disabling functionality available
11
- * only to moderator users.
12
- */
13
-var connection = null;
14
-var focusUserJid;
15
-
16
-function createExpBackoffTimer(step) {
17
-    var count = 1;
18
-    return function (reset) {
19
-        // Reset call
20
-        if (reset) {
21
-            count = 1;
22
-            return;
23
-        }
24
-        // Calculate next timeout
25
-        var timeout = Math.pow(2, count - 1);
26
-        count += 1;
27
-        return timeout * step;
28
-    };
29
-}
30
-
31
-var getNextTimeout = createExpBackoffTimer(1000);
32
-var getNextErrorTimeout = createExpBackoffTimer(1000);
33
-// External authentication stuff
34
-var externalAuthEnabled = false;
35
-// Sip gateway can be enabled by configuring Jigasi host in config.js or
36
-// it will be enabled automatically if focus detects the component through
37
-// service discovery.
38
-var sipGatewayEnabled;
39
-
40
-var eventEmitter = null;
41
-
42
-var Moderator = {
43
-    isModerator: function () {
44
-        return connection && connection.emuc.isModerator();
45
-    },
46
-
47
-    isPeerModerator: function (peerJid) {
48
-        return connection &&
49
-            connection.emuc.getMemberRole(peerJid) === 'moderator';
50
-    },
51
-
52
-    isExternalAuthEnabled: function () {
53
-        return externalAuthEnabled;
54
-    },
55
-
56
-    isSipGatewayEnabled: function () {
57
-        return sipGatewayEnabled;
58
-    },
59
-
60
-    setConnection: function (con) {
61
-        connection = con;
62
-    },
63
-
64
-    init: function (xmpp, emitter) {
65
-        this.xmppService = xmpp;
66
-        eventEmitter = emitter;
67
-
68
-        sipGatewayEnabled =
69
-            config.hosts && config.hosts.call_control !== undefined;
70
-
71
-        // Message listener that talks to POPUP window
72
-        function listener(event) {
73
-            if (event.data && event.data.sessionId) {
74
-                if (event.origin !== window.location.origin) {
75
-                    console.warn("Ignoring sessionId from different origin: " +
76
-                        event.origin);
77
-                    return;
78
-                }
79
-                localStorage.setItem('sessionId', event.data.sessionId);
80
-                // After popup is closed we will authenticate
81
-            }
82
-        }
83
-        // Register
84
-        if (window.addEventListener) {
85
-            window.addEventListener("message", listener, false);
86
-        } else {
87
-            window.attachEvent("onmessage", listener);
88
-        }
89
-    },
90
-
91
-    onMucMemberLeft: function (jid) {
92
-        console.info("Someone left is it focus ? " + jid);
93
-        var resource = Strophe.getResourceFromJid(jid);
94
-        if (resource === 'focus' && !this.xmppService.sessionTerminated) {
95
-            console.info(
96
-                "Focus has left the room - leaving conference");
97
-            //hangUp();
98
-            // We'd rather reload to have everything re-initialized
99
-            // FIXME: show some message before reload
100
-            location.reload();
101
-        }
102
-    },
103
-    
104
-    setFocusUserJid: function (focusJid) {
105
-        if (!focusUserJid) {
106
-            focusUserJid = focusJid;
107
-            console.info("Focus jid set to: " + focusUserJid);
108
-        }
109
-    },
110
-
111
-    getFocusUserJid: function () {
112
-        return focusUserJid;
113
-    },
114
-
115
-    getFocusComponent: function () {
116
-        // Get focus component address
117
-        var focusComponent = config.hosts.focus;
118
-        // If not specified use default: 'focus.domain'
119
-        if (!focusComponent) {
120
-            focusComponent = 'focus.' + config.hosts.domain;
121
-        }
122
-        return focusComponent;
123
-    },
124
-
125
-    createConferenceIq: function (roomName) {
126
-        // Generate create conference IQ
127
-        var elem = $iq({to: Moderator.getFocusComponent(), type: 'set'});
128
-
129
-        // Session Id used for authentication
130
-        var sessionId = localStorage.getItem('sessionId');
131
-        var machineUID = Settings.getSettings().uid;
132
-
133
-        console.info(
134
-            "Session ID: " + sessionId + " machine UID: " + machineUID);
135
-
136
-        elem.c('conference', {
137
-            xmlns: 'http://jitsi.org/protocol/focus',
138
-            room: roomName,
139
-            'machine-uid': machineUID
140
-        });
141
-
142
-        if (sessionId) {
143
-            elem.attrs({ 'session-id': sessionId});
144
-        }
145
-
146
-        if (config.hosts.bridge !== undefined) {
147
-            elem.c(
148
-                'property',
149
-                { name: 'bridge', value: config.hosts.bridge})
150
-                .up();
151
-        }
152
-        // Tell the focus we have Jigasi configured
153
-        if (config.hosts.call_control !== undefined) {
154
-            elem.c(
155
-                'property',
156
-                { name: 'call_control', value: config.hosts.call_control})
157
-                .up();
158
-        }
159
-        if (config.channelLastN !== undefined) {
160
-            elem.c(
161
-                'property',
162
-                { name: 'channelLastN', value: config.channelLastN})
163
-                .up();
164
-        }
165
-        if (config.adaptiveLastN !== undefined) {
166
-            elem.c(
167
-                'property',
168
-                { name: 'adaptiveLastN', value: config.adaptiveLastN})
169
-                .up();
170
-        }
171
-        if (config.adaptiveSimulcast !== undefined) {
172
-            elem.c(
173
-                'property',
174
-                { name: 'adaptiveSimulcast', value: config.adaptiveSimulcast})
175
-                .up();
176
-        }
177
-        if (config.openSctp !== undefined) {
178
-            elem.c(
179
-                'property',
180
-                { name: 'openSctp', value: config.openSctp})
181
-                .up();
182
-        }
183
-        if(config.startAudioMuted !== undefined)
184
-        {
185
-            elem.c(
186
-                'property',
187
-                { name: 'startAudioMuted', value: config.startAudioMuted})
188
-                .up();
189
-        }
190
-        if(config.startVideoMuted !== undefined)
191
-        {
192
-            elem.c(
193
-                'property',
194
-                { name: 'startVideoMuted', value: config.startVideoMuted})
195
-                .up();
196
-        }
197
-        elem.c(
198
-            'property',
199
-            { name: 'simulcastMode', value: 'rewriting'})
200
-            .up();
201
-        elem.up();
202
-        return elem;
203
-    },
204
-
205
-    parseSessionId: function (resultIq) {
206
-        var sessionId = $(resultIq).find('conference').attr('session-id');
207
-        if (sessionId) {
208
-            console.info('Received sessionId: ' + sessionId);
209
-            localStorage.setItem('sessionId', sessionId);
210
-        }
211
-    },
212
-
213
-    parseConfigOptions: function (resultIq) {
214
-
215
-        Moderator.setFocusUserJid(
216
-            $(resultIq).find('conference').attr('focusjid'));
217
-
218
-        var authenticationEnabled
219
-            = $(resultIq).find(
220
-                '>conference>property' +
221
-                '[name=\'authentication\'][value=\'true\']').length > 0;
222
-
223
-        console.info("Authentication enabled: " + authenticationEnabled);
224
-
225
-        externalAuthEnabled = $(resultIq).find(
226
-                '>conference>property' +
227
-                '[name=\'externalAuth\'][value=\'true\']').length > 0;
228
-
229
-        console.info('External authentication enabled: ' + externalAuthEnabled);
230
-
231
-        if (!externalAuthEnabled) {
232
-            // We expect to receive sessionId in 'internal' authentication mode
233
-            Moderator.parseSessionId(resultIq);
234
-        }
235
-
236
-        var authIdentity = $(resultIq).find('>conference').attr('identity');
237
-
238
-        eventEmitter.emit(AuthenticationEvents.IDENTITY_UPDATED,
239
-            authenticationEnabled, authIdentity);
240
-    
241
-        // Check if focus has auto-detected Jigasi component(this will be also
242
-        // included if we have passed our host from the config)
243
-        if ($(resultIq).find(
244
-            '>conference>property' +
245
-            '[name=\'sipGatewayEnabled\'][value=\'true\']').length) {
246
-            sipGatewayEnabled = true;
247
-        }
248
-    
249
-        console.info("Sip gateway enabled: " + sipGatewayEnabled);
250
-    },
251
-
252
-    // FIXME: we need to show the fact that we're waiting for the focus
253
-    // to the user(or that focus is not available)
254
-    allocateConferenceFocus: function (roomName, callback) {
255
-        // Try to use focus user JID from the config
256
-        Moderator.setFocusUserJid(config.focusUserJid);
257
-        // Send create conference IQ
258
-        var iq = Moderator.createConferenceIq(roomName);
259
-        var self = this;
260
-        connection.sendIQ(
261
-            iq,
262
-            function (result) {
263
-
264
-                // Setup config options
265
-                Moderator.parseConfigOptions(result);
266
-
267
-                if ('true' === $(result).find('conference').attr('ready')) {
268
-                    // Reset both timers
269
-                    getNextTimeout(true);
270
-                    getNextErrorTimeout(true);
271
-                    // Exec callback
272
-                    callback();
273
-                } else {
274
-                    var waitMs = getNextTimeout();
275
-                    console.info("Waiting for the focus... " + waitMs);
276
-                    // Reset error timeout
277
-                    getNextErrorTimeout(true);
278
-                    window.setTimeout(
279
-                        function () {
280
-                            Moderator.allocateConferenceFocus(
281
-                                roomName, callback);
282
-                        }, waitMs);
283
-                }
284
-            },
285
-            function (error) {
286
-                // Invalid session ? remove and try again
287
-                // without session ID to get a new one
288
-                var invalidSession
289
-                    = $(error).find('>error>session-invalid').length;
290
-                if (invalidSession) {
291
-                    console.info("Session expired! - removing");
292
-                    localStorage.removeItem("sessionId");
293
-                }
294
-                if ($(error).find('>error>graceful-shutdown').length) {
295
-                    eventEmitter.emit(XMPPEvents.GRACEFUL_SHUTDOWN);
296
-                    return;
297
-                }
298
-                // Check for error returned by the reservation system
299
-                var reservationErr = $(error).find('>error>reservation-error');
300
-                if (reservationErr.length) {
301
-                    // Trigger error event
302
-                    var errorCode = reservationErr.attr('error-code');
303
-                    var errorMsg;
304
-                    if ($(error).find('>error>text')) {
305
-                        errorMsg = $(error).find('>error>text').text();
306
-                    }
307
-                    eventEmitter.emit(
308
-                        XMPPEvents.RESERVATION_ERROR, errorCode, errorMsg);
309
-                    return;
310
-                }
311
-                // Not authorized to create new room
312
-                if ($(error).find('>error>not-authorized').length) {
313
-                    console.warn("Unauthorized to start the conference", error);
314
-                    var toDomain
315
-                        = Strophe.getDomainFromJid(error.getAttribute('to'));
316
-                    if (toDomain !== config.hosts.anonymousdomain) {
317
-                        // FIXME: "is external" should come either from
318
-                        // the focus or config.js
319
-                        externalAuthEnabled = true;
320
-                    }
321
-                    eventEmitter.emit(
322
-                        XMPPEvents.AUTHENTICATION_REQUIRED,
323
-                        function () {
324
-                            Moderator.allocateConferenceFocus(
325
-                                roomName, callback);
326
-                        });
327
-                    return;
328
-                }
329
-                var waitMs = getNextErrorTimeout();
330
-                console.error("Focus error, retry after " + waitMs, error);
331
-                // Show message
332
-                var focusComponent = Moderator.getFocusComponent();
333
-                var retrySec = waitMs / 1000;
334
-                // FIXME: message is duplicated ?
335
-                // Do not show in case of session invalid
336
-                // which means just a retry
337
-                if (!invalidSession) {
338
-                    eventEmitter.emit(XMPPEvents.FOCUS_DISCONNECTED,
339
-                        focusComponent, retrySec);
340
-                }
341
-                // Reset response timeout
342
-                getNextTimeout(true);
343
-                window.setTimeout(
344
-                    function () {
345
-                        Moderator.allocateConferenceFocus(roomName, callback);
346
-                    }, waitMs);
347
-            }
348
-        );
349
-    },
350
-
351
-    getLoginUrl: function (roomName, urlCallback) {
352
-        var iq = $iq({to: Moderator.getFocusComponent(), type: 'get'});
353
-        iq.c('login-url', {
354
-            xmlns: 'http://jitsi.org/protocol/focus',
355
-            room: roomName,
356
-            'machine-uid': Settings.getSettings().uid
357
-        });
358
-        connection.sendIQ(
359
-            iq,
360
-            function (result) {
361
-                var url = $(result).find('login-url').attr('url');
362
-                url = url = decodeURIComponent(url);
363
-                if (url) {
364
-                    console.info("Got auth url: " + url);
365
-                    urlCallback(url);
366
-                } else {
367
-                    console.error(
368
-                        "Failed to get auth url from the focus", result);
369
-                }
370
-            },
371
-            function (error) {
372
-                console.error("Get auth url error", error);
373
-            }
374
-        );
375
-    },
376
-    getPopupLoginUrl: function (roomName, urlCallback) {
377
-        var iq = $iq({to: Moderator.getFocusComponent(), type: 'get'});
378
-        iq.c('login-url', {
379
-            xmlns: 'http://jitsi.org/protocol/focus',
380
-            room: roomName,
381
-            'machine-uid': Settings.getSettings().uid,
382
-            popup: true
383
-        });
384
-        connection.sendIQ(
385
-            iq,
386
-            function (result) {
387
-                var url = $(result).find('login-url').attr('url');
388
-                url = url = decodeURIComponent(url);
389
-                if (url) {
390
-                    console.info("Got POPUP auth url: " + url);
391
-                    urlCallback(url);
392
-                } else {
393
-                    console.error(
394
-                        "Failed to get POPUP auth url from the focus", result);
395
-                }
396
-            },
397
-            function (error) {
398
-                console.error('Get POPUP auth url error', error);
399
-            }
400
-        );
401
-    },
402
-    logout: function (callback) {
403
-        var iq = $iq({to: Moderator.getFocusComponent(), type: 'set'});
404
-        var sessionId = localStorage.getItem('sessionId');
405
-        if (!sessionId) {
406
-            callback();
407
-            return;
408
-        }
409
-        iq.c('logout', {
410
-            xmlns: 'http://jitsi.org/protocol/focus',
411
-            'session-id': sessionId
412
-        });
413
-        connection.sendIQ(
414
-            iq,
415
-            function (result) {
416
-                var logoutUrl = $(result).find('logout').attr('logout-url');
417
-                if (logoutUrl) {
418
-                    logoutUrl = decodeURIComponent(logoutUrl);
419
-                }
420
-                console.info("Log out OK, url: " + logoutUrl, result);
421
-                localStorage.removeItem('sessionId');
422
-                callback(logoutUrl);
423
-            },
424
-            function (error) {
425
-                console.error("Logout error", error);
426
-            }
427
-        );
428
-    }
429
-};
430
-
431
-module.exports = Moderator;
432
-
433
-
434
-

+ 0
- 178
modules/xmpp/recording.js ファイルの表示

@@ -1,178 +0,0 @@
1
-/* global $, $iq, config, connection, focusMucJid, messageHandler,
2
-   Toolbar, Util */
3
-var Moderator = require("./moderator");
4
-
5
-
6
-var recordingToken = null;
7
-var recordingEnabled;
8
-
9
-/**
10
- * Whether to use a jirecon component for recording, or use the videobridge
11
- * through COLIBRI.
12
- */
13
-var useJirecon;
14
-
15
-/**
16
- * The ID of the jirecon recording session. Jirecon generates it when we
17
- * initially start recording, and it needs to be used in subsequent requests
18
- * to jirecon.
19
- */
20
-var jireconRid = null;
21
-
22
-/**
23
- * The callback to update the recording button. Currently used from colibri
24
- * after receiving a pending status.
25
- */
26
-var recordingStateChangeCallback = null;
27
-
28
-function setRecordingToken(token) {
29
-    recordingToken = token;
30
-}
31
-
32
-function setRecordingJirecon(state, token, callback, connection) {
33
-    if (state == recordingEnabled){
34
-        return;
35
-    }
36
-
37
-    var iq = $iq({to: config.hosts.jirecon, type: 'set'})
38
-        .c('recording', {xmlns: 'http://jitsi.org/protocol/jirecon',
39
-            action: (state === 'on') ? 'start' : 'stop',
40
-            mucjid: connection.emuc.roomjid});
41
-    if (state === 'off'){
42
-        iq.attrs({rid: jireconRid});
43
-    }
44
-
45
-    console.log('Start recording');
46
-
47
-    connection.sendIQ(
48
-        iq,
49
-        function (result) {
50
-            // TODO wait for an IQ with the real status, since this is
51
-            // provisional?
52
-            jireconRid = $(result).find('recording').attr('rid');
53
-            console.log('Recording ' +
54
-                ((state === 'on') ? 'started' : 'stopped') +
55
-                '(jirecon)' + result);
56
-            recordingEnabled = state;
57
-            if (state === 'off'){
58
-                jireconRid = null;
59
-            }
60
-
61
-            callback(state);
62
-        },
63
-        function (error) {
64
-            console.log('Failed to start recording, error: ', error);
65
-            callback(recordingEnabled);
66
-        });
67
-}
68
-
69
-// Sends a COLIBRI message which enables or disables (according to 'state')
70
-// the recording on the bridge. Waits for the result IQ and calls 'callback'
71
-// with the new recording state, according to the IQ.
72
-function setRecordingColibri(state, token, callback, connection) {
73
-    var elem = $iq({to: connection.emuc.focusMucJid, type: 'set'});
74
-    elem.c('conference', {
75
-        xmlns: 'http://jitsi.org/protocol/colibri'
76
-    });
77
-    elem.c('recording', {state: state, token: token});
78
-
79
-    connection.sendIQ(elem,
80
-        function (result) {
81
-            console.log('Set recording "', state, '". Result:', result);
82
-            var recordingElem = $(result).find('>conference>recording');
83
-            var newState = recordingElem.attr('state');
84
-
85
-            recordingEnabled = newState;
86
-            callback(newState);
87
-
88
-            if (newState === 'pending' && !recordingStateChangeCallback) {
89
-                recordingStateChangeCallback = callback;
90
-                connection.addHandler(function(iq){
91
-                    var state = $(iq).find('recording').attr('state');
92
-                    if (state)
93
-                        recordingStateChangeCallback(state);
94
-                }, 'http://jitsi.org/protocol/colibri', 'iq', null, null, null);
95
-            }
96
-        },
97
-        function (error) {
98
-            console.warn(error);
99
-            callback(recordingEnabled);
100
-        }
101
-    );
102
-}
103
-
104
-function setRecording(state, token, callback, connection) {
105
-    if (useJirecon){
106
-        setRecordingJirecon(state, token, callback, connection);
107
-    } else {
108
-        setRecordingColibri(state, token, callback, connection);
109
-    }
110
-}
111
-
112
-var Recording = {
113
-    init: function () {
114
-        useJirecon = config.hosts &&
115
-            (typeof config.hosts.jirecon != "undefined");
116
-    },
117
-    toggleRecording: function (tokenEmptyCallback,
118
-                               recordingStateChangeCallback,
119
-                               connection) {
120
-        if (!Moderator.isModerator()) {
121
-            console.log(
122
-                    'non-focus, or conference not yet organized:' +
123
-                    ' not enabling recording');
124
-            return;
125
-        }
126
-
127
-        var self = this;
128
-        // Jirecon does not (currently) support a token.
129
-        if (!recordingToken && !useJirecon) {
130
-            tokenEmptyCallback(function (value) {
131
-                setRecordingToken(value);
132
-                self.toggleRecording(tokenEmptyCallback,
133
-                                     recordingStateChangeCallback,
134
-                                     connection);
135
-            });
136
-
137
-            return;
138
-        }
139
-
140
-        var oldState = recordingEnabled;
141
-        var newState = (oldState === 'off' || !oldState) ? 'on' : 'off';
142
-
143
-        setRecording(newState,
144
-            recordingToken,
145
-            function (state) {
146
-                console.log("New recording state: ", state);
147
-                if (state === oldState) {
148
-                    // FIXME: new focus:
149
-                    // this will not work when moderator changes
150
-                    // during active session. Then it will assume that
151
-                    // recording status has changed to true, but it might have
152
-                    // been already true(and we only received actual status from
153
-                    // the focus).
154
-                    //
155
-                    // SO we start with status null, so that it is initialized
156
-                    // here and will fail only after second click, so if invalid
157
-                    // token was used we have to press the button twice before
158
-                    // current status will be fetched and token will be reset.
159
-                    //
160
-                    // Reliable way would be to return authentication error.
161
-                    // Or status update when moderator connects.
162
-                    // Or we have to stop recording session when current
163
-                    // moderator leaves the room.
164
-
165
-                    // Failed to change, reset the token because it might
166
-                    // have been wrong
167
-                    setRecordingToken(null);
168
-                }
169
-                recordingStateChangeCallback(state);
170
-
171
-            },
172
-            connection
173
-        );
174
-    }
175
-
176
-};
177
-
178
-module.exports = Recording;

+ 0
- 706
modules/xmpp/strophe.emuc.js ファイルの表示

@@ -1,706 +0,0 @@
1
-/* jshint -W117 */
2
-/* a simple MUC connection plugin
3
- * can only handle a single MUC room
4
- */
5
-var XMPPEvents = require("../../service/xmpp/XMPPEvents");
6
-var Moderator = require("./moderator");
7
-
8
-module.exports = function(XMPP, eventEmitter) {
9
-    Strophe.addConnectionPlugin('emuc', {
10
-        connection: null,
11
-        roomjid: null,
12
-        myroomjid: null,
13
-        members: {},
14
-        list_members: [], // so we can elect a new focus
15
-        presMap: {},
16
-        preziMap: {},
17
-        lastPresenceMap: {},
18
-        joined: false,
19
-        isOwner: false,
20
-        role: null,
21
-        focusMucJid: null,
22
-        bridgeIsDown: false,
23
-        init: function (conn) {
24
-            this.connection = conn;
25
-        },
26
-        initPresenceMap: function (myroomjid) {
27
-            this.presMap['to'] = myroomjid;
28
-            this.presMap['xns'] = 'http://jabber.org/protocol/muc';
29
-            if (APP.RTC.localAudio && APP.RTC.localAudio.isMuted()) {
30
-                this.addAudioInfoToPresence(true);
31
-            }
32
-            if (APP.RTC.localVideo && APP.RTC.localVideo.isMuted()) {
33
-                this.addVideoInfoToPresence(true);
34
-            }
35
-        },
36
-        doJoin: function (jid, password) {
37
-            this.myroomjid = jid;
38
-
39
-            console.info("Joined MUC as " + this.myroomjid);
40
-
41
-            this.initPresenceMap(this.myroomjid);
42
-
43
-            if (!this.roomjid) {
44
-                this.roomjid = Strophe.getBareJidFromJid(jid);
45
-                // add handlers (just once)
46
-                this.connection.addHandler(this.onPresence.bind(this), null, 'presence', null, null, this.roomjid, {matchBare: true});
47
-                this.connection.addHandler(this.onPresenceUnavailable.bind(this), null, 'presence', 'unavailable', null, this.roomjid, {matchBare: true});
48
-                this.connection.addHandler(this.onPresenceError.bind(this), null, 'presence', 'error', null, this.roomjid, {matchBare: true});
49
-                this.connection.addHandler(this.onMessage.bind(this), null, 'message', null, null, this.roomjid, {matchBare: true});
50
-            }
51
-            if (password !== undefined) {
52
-                this.presMap['password'] = password;
53
-            }
54
-            this.sendPresence();
55
-        },
56
-        doLeave: function () {
57
-            console.log("do leave", this.myroomjid);
58
-            var pres = $pres({to: this.myroomjid, type: 'unavailable' });
59
-            this.presMap.length = 0;
60
-            this.connection.send(pres);
61
-        },
62
-        createNonAnonymousRoom: function () {
63
-            // http://xmpp.org/extensions/xep-0045.html#createroom-reserved
64
-
65
-            var getForm = $iq({type: 'get', to: this.roomjid})
66
-                .c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'})
67
-                .c('x', {xmlns: 'jabber:x:data', type: 'submit'});
68
-
69
-            var self = this;
70
-
71
-            this.connection.sendIQ(getForm, function (form) {
72
-
73
-                if (!$(form).find(
74
-                        '>query>x[xmlns="jabber:x:data"]' +
75
-                        '>field[var="muc#roomconfig_whois"]').length) {
76
-
77
-                    console.error('non-anonymous rooms not supported');
78
-                    return;
79
-                }
80
-
81
-                var formSubmit = $iq({to: this.roomjid, type: 'set'})
82
-                    .c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'});
83
-
84
-                formSubmit.c('x', {xmlns: 'jabber:x:data', type: 'submit'});
85
-
86
-                formSubmit.c('field', {'var': 'FORM_TYPE'})
87
-                    .c('value')
88
-                    .t('http://jabber.org/protocol/muc#roomconfig').up().up();
89
-
90
-                formSubmit.c('field', {'var': 'muc#roomconfig_whois'})
91
-                    .c('value').t('anyone').up().up();
92
-
93
-                self.connection.sendIQ(formSubmit);
94
-
95
-            }, function (error) {
96
-                console.error("Error getting room configuration form");
97
-            });
98
-        },
99
-        onPresence: function (pres) {
100
-            var from = pres.getAttribute('from');
101
-
102
-            // What is this for? A workaround for something?
103
-            if (pres.getAttribute('type')) {
104
-                return true;
105
-            }
106
-
107
-            // Parse etherpad tag.
108
-            var etherpad = $(pres).find('>etherpad');
109
-            if (etherpad.length) {
110
-                if (config.etherpad_base) {
111
-                    eventEmitter.emit(XMPPEvents.ETHERPAD, etherpad.text());
112
-                }
113
-            }
114
-
115
-            var url;
116
-            // Parse prezi tag.
117
-            var presentation = $(pres).find('>prezi');
118
-            if (presentation.length) {
119
-                url = presentation.attr('url');
120
-                var current = presentation.find('>current').text();
121
-
122
-                console.log('presentation info received from', from, url);
123
-
124
-                if (this.preziMap[from] == null) {
125
-                    this.preziMap[from] = url;
126
-
127
-                    $(document).trigger('presentationadded.muc', [from, url, current]);
128
-                }
129
-                else {
130
-                    $(document).trigger('gotoslide.muc', [from, url, current]);
131
-                }
132
-            }
133
-            else if (this.preziMap[from] != null) {
134
-                url = this.preziMap[from];
135
-                delete this.preziMap[from];
136
-                $(document).trigger('presentationremoved.muc', [from, url]);
137
-            }
138
-
139
-            // store the last presence for participant
140
-            this.lastPresenceMap[from] = {};
141
-
142
-            // Parse audio info tag.
143
-            var audioMuted = $(pres).find('>audiomuted');
144
-            if (audioMuted.length) {
145
-                eventEmitter.emit(XMPPEvents.PARTICIPANT_AUDIO_MUTED,
146
-                    from, (audioMuted.text() === "true"));
147
-            }
148
-
149
-            // Parse video info tag.
150
-            var videoMuted = $(pres).find('>videomuted');
151
-            if (videoMuted.length) {
152
-                var value = (videoMuted.text() === "true");
153
-                this.lastPresenceMap[from].videoMuted = value;
154
-                eventEmitter.emit(XMPPEvents.PARTICIPANT_VIDEO_MUTED, from, value);
155
-            }
156
-
157
-            var startMuted = $(pres).find('>startmuted');
158
-            if (startMuted.length && Moderator.isPeerModerator(from)) {
159
-                eventEmitter.emit(XMPPEvents.START_MUTED_SETTING_CHANGED,
160
-                    startMuted.attr("audio") === "true",
161
-                    startMuted.attr("video") === "true");
162
-            }
163
-
164
-            var devices = $(pres).find('>devices');
165
-            if(devices.length)
166
-            {
167
-                var audio = devices.find('>audio');
168
-                var video = devices.find('>video');
169
-                var devicesValues = {audio: false, video: false};
170
-                if(audio.length && audio.text() === "true")
171
-                {
172
-                    devicesValues.audio = true;
173
-                }
174
-
175
-                if(video.length && video.text() === "true")
176
-                {
177
-                    devicesValues.video = true;
178
-                }
179
-                eventEmitter.emit(XMPPEvents.DEVICE_AVAILABLE,
180
-                    Strophe.getResourceFromJid(from), devicesValues);
181
-            }
182
-
183
-            var videoType = $(pres).find('>videoType');
184
-            if (videoType.length)
185
-            {
186
-                if (videoType.text().length)
187
-                {
188
-                    eventEmitter.emit(XMPPEvents.PARTICIPANT_VIDEO_TYPE_CHANGED,
189
-                        Strophe.getResourceFromJid(from), videoType.text());
190
-                }
191
-            }
192
-
193
-            var stats = $(pres).find('>stats');
194
-            if (stats.length) {
195
-                var statsObj = {};
196
-                Strophe.forEachChild(stats[0], "stat", function (el) {
197
-                    statsObj[el.getAttribute("name")] = el.getAttribute("value");
198
-                });
199
-                eventEmitter.emit(XMPPEvents.REMOTE_STATS, from, statsObj);
200
-            }
201
-
202
-            // Parse status.
203
-            if ($(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="201"]').length) {
204
-                this.isOwner = true;
205
-                this.createNonAnonymousRoom();
206
-            }
207
-
208
-            // Parse roles.
209
-            var member = {};
210
-            member.show = $(pres).find('>show').text();
211
-            member.status = $(pres).find('>status').text();
212
-            var tmp = $(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>item');
213
-            member.affiliation = tmp.attr('affiliation');
214
-            member.role = tmp.attr('role');
215
-
216
-            // Focus recognition
217
-            member.jid = tmp.attr('jid');
218
-            member.isFocus = false;
219
-            if (member.jid
220
-                && member.jid.indexOf(Moderator.getFocusUserJid() + "/") == 0) {
221
-                member.isFocus = true;
222
-            }
223
-
224
-            var nicktag = $(pres).find('>nick[xmlns="http://jabber.org/protocol/nick"]');
225
-            member.displayName = (nicktag.length > 0 ? nicktag.text() : null);
226
-
227
-            if (from == this.myroomjid) {
228
-                if (member.affiliation == 'owner') this.isOwner = true;
229
-                if (this.role !== member.role) {
230
-                    this.role = member.role;
231
-
232
-                    eventEmitter.emit(XMPPEvents.LOCAL_ROLE_CHANGED,
233
-                        from, member, pres, Moderator.isModerator());
234
-                }
235
-                if (!this.joined) {
236
-                    this.joined = true;
237
-                    console.log("(TIME) MUC joined:\t",
238
-                                window.performance.now());
239
-                    eventEmitter.emit(XMPPEvents.MUC_JOINED, from, member);
240
-                    this.list_members.push(from);
241
-                }
242
-            } else if (this.members[from] === undefined) {
243
-                // new participant
244
-                this.members[from] = member;
245
-                this.list_members.push(from);
246
-                console.log('entered', from, member);
247
-                if (member.isFocus) {
248
-                    this.focusMucJid = from;
249
-                    console.info("Ignore focus: " + from + ", real JID: " + member.jid);
250
-                }
251
-                else {
252
-                    var id = $(pres).find('>userId').text();
253
-                    var email = $(pres).find('>email');
254
-                    if (email.length > 0) {
255
-                        id = email.text();
256
-                    }
257
-                    eventEmitter.emit(XMPPEvents.MUC_MEMBER_JOINED, from, id, member.displayName);
258
-                }
259
-            } else {
260
-                // Presence update for existing participant
261
-                // Watch role change:
262
-                if (this.members[from].role != member.role) {
263
-                    this.members[from].role = member.role;
264
-                    eventEmitter.emit(XMPPEvents.MUC_ROLE_CHANGED,
265
-                        member.role, member.displayName);
266
-                }
267
-
268
-                // store the new
269
-                if(member.displayName)
270
-                    this.members[from].displayName = member.displayName;
271
-            }
272
-
273
-            // Always trigger presence to update bindings
274
-            this.parsePresence(from, member, pres);
275
-
276
-            // Trigger status message update
277
-            if (member.status) {
278
-                eventEmitter.emit(XMPPEvents.PRESENCE_STATUS, from, member);
279
-            }
280
-
281
-            return true;
282
-        },
283
-        onPresenceUnavailable: function (pres) {
284
-            var from = pres.getAttribute('from');
285
-            // room destroyed ?
286
-            if ($(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]' +
287
-                             '>destroy').length) {
288
-                var reason;
289
-                var reasonSelect = $(pres).find(
290
-                    '>x[xmlns="http://jabber.org/protocol/muc#user"]' +
291
-                    '>destroy>reason');
292
-                if (reasonSelect.length) {
293
-                    reason = reasonSelect.text();
294
-                }
295
-                XMPP.disposeConference(false);
296
-                eventEmitter.emit(XMPPEvents.MUC_DESTROYED, reason);
297
-                return true;
298
-            }
299
-
300
-            // Status code 110 indicates that this notification is "self-presence".
301
-            if (!$(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="110"]').length) {
302
-                delete this.members[from];
303
-                this.list_members.splice(this.list_members.indexOf(from), 1);
304
-                this.onParticipantLeft(from);
305
-            }
306
-            // If the status code is 110 this means we're leaving and we would like
307
-            // to remove everyone else from our view, so we trigger the event.
308
-            else if (this.list_members.length > 1) {
309
-                for (var i = 0; i < this.list_members.length; i++) {
310
-                    var member = this.list_members[i];
311
-                    delete this.members[i];
312
-                    this.list_members.splice(i, 1);
313
-                    this.onParticipantLeft(member);
314
-                }
315
-            }
316
-            if ($(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="307"]').length) {
317
-                $(document).trigger('kicked.muc', [from]);
318
-                if (this.myroomjid === from) {
319
-                    XMPP.disposeConference(false);
320
-                    eventEmitter.emit(XMPPEvents.KICKED);
321
-                }
322
-            }
323
-
324
-            if (this.lastPresenceMap[from] != null) {
325
-                delete this.lastPresenceMap[from];
326
-            }
327
-
328
-            return true;
329
-        },
330
-        onPresenceError: function (pres) {
331
-            var from = pres.getAttribute('from');
332
-            if ($(pres).find('>error[type="auth"]>not-authorized[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]').length) {
333
-                console.log('on password required', from);
334
-                var self = this;
335
-                eventEmitter.emit(XMPPEvents.PASSWORD_REQUIRED, function (value) {
336
-                    self.doJoin(from, value);
337
-                });
338
-            } else if ($(pres).find(
339
-                '>error[type="cancel"]>not-allowed[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]').length) {
340
-                var toDomain = Strophe.getDomainFromJid(pres.getAttribute('to'));
341
-                if (toDomain === config.hosts.anonymousdomain) {
342
-                    // enter the room by replying with 'not-authorized'. This would
343
-                    // result in reconnection from authorized domain.
344
-                    // We're either missing Jicofo/Prosody config for anonymous
345
-                    // domains or something is wrong.
346
-//                    XMPP.promptLogin();
347
-                    eventEmitter.emit(XMPPEvents.ROOM_JOIN_ERROR, pres);
348
-
349
-                } else {
350
-                    console.warn('onPresError ', pres);
351
-                    eventEmitter.emit(XMPPEvents.ROOM_CONNECT_ERROR, pres);
352
-                }
353
-            } else {
354
-                console.warn('onPresError ', pres);
355
-                eventEmitter.emit(XMPPEvents.ROOM_CONNECT_ERROR, pres);
356
-            }
357
-            return true;
358
-        },
359
-        sendMessage: function (body, nickname) {
360
-            var msg = $msg({to: this.roomjid, type: 'groupchat'});
361
-            msg.c('body', body).up();
362
-            if (nickname) {
363
-                msg.c('nick', {xmlns: 'http://jabber.org/protocol/nick'}).t(nickname).up().up();
364
-            }
365
-            this.connection.send(msg);
366
-            eventEmitter.emit(XMPPEvents.SENDING_CHAT_MESSAGE, body);
367
-        },
368
-        setSubject: function (subject) {
369
-            var msg = $msg({to: this.roomjid, type: 'groupchat'});
370
-            msg.c('subject', subject);
371
-            this.connection.send(msg);
372
-            console.log("topic changed to " + subject);
373
-        },
374
-        onMessage: function (msg) {
375
-            // FIXME: this is a hack. but jingle on muc makes nickchanges hard
376
-            var from = msg.getAttribute('from');
377
-            var nick =
378
-                $(msg).find('>nick[xmlns="http://jabber.org/protocol/nick"]')
379
-                    .text() ||
380
-                Strophe.getResourceFromJid(from);
381
-
382
-            var txt = $(msg).find('>body').text();
383
-            var type = msg.getAttribute("type");
384
-            if (type == "error") {
385
-                eventEmitter.emit(XMPPEvents.CHAT_ERROR_RECEIVED,
386
-                    $(msg).find('>text').text(), txt);
387
-                return true;
388
-            }
389
-
390
-            var subject = $(msg).find('>subject');
391
-            if (subject.length) {
392
-                var subjectText = subject.text();
393
-                if (subjectText || subjectText == "") {
394
-                    eventEmitter.emit(XMPPEvents.SUBJECT_CHANGED, subjectText);
395
-                    console.log("Subject is changed to " + subjectText);
396
-                }
397
-            }
398
-
399
-            // xep-0203 delay
400
-            var stamp = $(msg).find('>delay').attr('stamp');
401
-
402
-            if (!stamp) {
403
-                // or xep-0091 delay, UTC timestamp
404
-                stamp = $(msg).find('>[xmlns="jabber:x:delay"]').attr('stamp');
405
-
406
-                if (stamp) {
407
-                    // the format is CCYYMMDDThh:mm:ss
408
-                    var dateParts = stamp.match(/(\d{4})(\d{2})(\d{2}T\d{2}:\d{2}:\d{2})/);
409
-                    stamp = dateParts[1] + "-" + dateParts[2] + "-" + dateParts[3] + "Z";
410
-                }
411
-            }
412
-
413
-            if (txt) {
414
-                console.log('chat', nick, txt);
415
-                eventEmitter.emit(XMPPEvents.MESSAGE_RECEIVED,
416
-                    from, nick, txt, this.myroomjid, stamp);
417
-            }
418
-            return true;
419
-        },
420
-        lockRoom: function (key, onSuccess, onError, onNotSupported) {
421
-            //http://xmpp.org/extensions/xep-0045.html#roomconfig
422
-            var ob = this;
423
-            this.connection.sendIQ($iq({to: this.roomjid, type: 'get'}).c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'}),
424
-                function (res) {
425
-                    if ($(res).find('>query>x[xmlns="jabber:x:data"]>field[var="muc#roomconfig_roomsecret"]').length) {
426
-                        var formsubmit = $iq({to: ob.roomjid, type: 'set'}).c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'});
427
-                        formsubmit.c('x', {xmlns: 'jabber:x:data', type: 'submit'});
428
-                        formsubmit.c('field', {'var': 'FORM_TYPE'}).c('value').t('http://jabber.org/protocol/muc#roomconfig').up().up();
429
-                        formsubmit.c('field', {'var': 'muc#roomconfig_roomsecret'}).c('value').t(key).up().up();
430
-                        // Fixes a bug in prosody 0.9.+ https://code.google.com/p/lxmppd/issues/detail?id=373
431
-                        formsubmit.c('field', {'var': 'muc#roomconfig_whois'}).c('value').t('anyone').up().up();
432
-                        // FIXME: is muc#roomconfig_passwordprotectedroom required?
433
-                        ob.connection.sendIQ(formsubmit,
434
-                            onSuccess,
435
-                            onError);
436
-                    } else {
437
-                        onNotSupported();
438
-                    }
439
-                }, onError);
440
-        },
441
-        kick: function (jid) {
442
-            var kickIQ = $iq({to: this.roomjid, type: 'set'})
443
-                .c('query', {xmlns: 'http://jabber.org/protocol/muc#admin'})
444
-                .c('item', {nick: Strophe.getResourceFromJid(jid), role: 'none'})
445
-                .c('reason').t('You have been kicked.').up().up().up();
446
-
447
-            this.connection.sendIQ(
448
-                kickIQ,
449
-                function (result) {
450
-                    console.log('Kick participant with jid: ', jid, result);
451
-                },
452
-                function (error) {
453
-                    console.log('Kick participant error: ', error);
454
-                });
455
-        },
456
-        sendPresence: function () {
457
-            if (!this.presMap['to']) {
458
-                // Too early to send presence - not initialized
459
-                return;
460
-            }
461
-            var pres = $pres({to: this.presMap['to'] });
462
-            pres.c('x', {xmlns: this.presMap['xns']});
463
-
464
-            if (this.presMap['password']) {
465
-                pres.c('password').t(this.presMap['password']).up();
466
-            }
467
-
468
-            pres.up();
469
-
470
-            // Send XEP-0115 'c' stanza that contains our capabilities info
471
-            if (this.connection.caps) {
472
-                this.connection.caps.node = config.clientNode;
473
-                pres.c('c', this.connection.caps.generateCapsAttrs()).up();
474
-            }
475
-
476
-            pres.c('user-agent', {xmlns: 'http://jitsi.org/jitmeet/user-agent'})
477
-                .t(navigator.userAgent).up();
478
-
479
-            if (this.presMap['bridgeIsDown']) {
480
-                pres.c('bridgeIsDown').up();
481
-            }
482
-
483
-            if (this.presMap['email']) {
484
-                pres.c('email').t(this.presMap['email']).up();
485
-            }
486
-
487
-            if (this.presMap['userId']) {
488
-                pres.c('userId').t(this.presMap['userId']).up();
489
-            }
490
-
491
-            if (this.presMap['displayName']) {
492
-                // XEP-0172
493
-                pres.c('nick', {xmlns: 'http://jabber.org/protocol/nick'})
494
-                    .t(this.presMap['displayName']).up();
495
-            }
496
-
497
-            if(this.presMap["devices"])
498
-            {
499
-                pres.c('devices').c('audio').t(this.presMap['devices'].audio).up()
500
-                    .c('video').t(this.presMap['devices'].video).up().up();
501
-            }
502
-            if (this.presMap['audions']) {
503
-                pres.c('audiomuted', {xmlns: this.presMap['audions']})
504
-                    .t(this.presMap['audiomuted']).up();
505
-            }
506
-
507
-            if (this.presMap['videons']) {
508
-                pres.c('videomuted', {xmlns: this.presMap['videons']})
509
-                    .t(this.presMap['videomuted']).up();
510
-            }
511
-
512
-            if (this.presMap['videoTypeNs']) {
513
-                pres.c('videoType', { xmlns: this.presMap['videoTypeNs'] })
514
-                    .t(this.presMap['videoType']).up();
515
-            }
516
-
517
-            if (this.presMap['statsns']) {
518
-                var stats = pres.c('stats', {xmlns: this.presMap['statsns']});
519
-                for (var stat in this.presMap["stats"])
520
-                    if (this.presMap["stats"][stat] != null)
521
-                        stats.c("stat", {name: stat, value: this.presMap["stats"][stat]}).up();
522
-                pres.up();
523
-            }
524
-
525
-            if (this.presMap['prezins']) {
526
-                pres.c('prezi',
527
-                    {xmlns: this.presMap['prezins'],
528
-                        'url': this.presMap['preziurl']})
529
-                    .c('current').t(this.presMap['prezicurrent']).up().up();
530
-            }
531
-
532
-            // This is only for backward compatibility with clients which
533
-            // don't support getting sources from Jingle (i.e. jirecon).
534
-            if (this.presMap['medians']) {
535
-                pres.c('media', {xmlns: this.presMap['medians']});
536
-                var sourceNumber = 0;
537
-                Object.keys(this.presMap).forEach(function (key) {
538
-                    if (key.indexOf('source') >= 0) {
539
-                        sourceNumber++;
540
-                    }
541
-                });
542
-                if (sourceNumber > 0) {
543
-                    for (var i = 1; i <= sourceNumber / 3; i++) {
544
-                        pres.c('source',
545
-                            {
546
-                                type: this.presMap['source' + i + '_type'],
547
-                                ssrc: this.presMap['source' + i + '_ssrc'],
548
-                                direction: this.presMap['source' + i + '_direction']
549
-                                || 'sendrecv'
550
-                            }
551
-                        ).up();
552
-                    }
553
-                }
554
-                pres.up();
555
-            }
556
-
557
-            if(this.presMap["startMuted"] !== undefined)
558
-            {
559
-                pres.c("startmuted", {audio: this.presMap["startMuted"].audio,
560
-                    video: this.presMap["startMuted"].video,
561
-                    xmlns: "http://jitsi.org/jitmeet/start-muted"});
562
-                delete this.presMap["startMuted"];
563
-            }
564
-
565
-            if (config.token) {
566
-                pres.c('token', { xmlns: 'http://jitsi.org/jitmeet/auth-token'}).t(config.token).up();
567
-            }
568
-
569
-            pres.up();
570
-            this.connection.send(pres);
571
-        },
572
-        addDisplayNameToPresence: function (displayName) {
573
-            this.presMap['displayName'] = displayName;
574
-        },
575
-        // This is only for backward compatibility with clients which
576
-        // don't support getting sources from Jingle (i.e. jirecon).
577
-        addMediaToPresence: function (sourceNumber, mtype, ssrcs, direction) {
578
-            if (!this.presMap['medians'])
579
-                this.presMap['medians'] = 'http://estos.de/ns/mjs';
580
-
581
-            this.presMap['source' + sourceNumber + '_type'] = mtype;
582
-            this.presMap['source' + sourceNumber + '_ssrc'] = ssrcs;
583
-            this.presMap['source' + sourceNumber + '_direction'] = direction;
584
-        },
585
-        // This is only for backward compatibility with clients which
586
-        // don't support getting sources from Jingle (i.e. jirecon).
587
-        clearPresenceMedia: function () {
588
-            var self = this;
589
-            Object.keys(this.presMap).forEach(function (key) {
590
-                if (key.indexOf('source') != -1) {
591
-                    delete self.presMap[key];
592
-                }
593
-            });
594
-        },
595
-        addDevicesToPresence: function (devices) {
596
-            this.presMap['devices'] = devices;
597
-        },
598
-        /**
599
-         * Adds the info about the type of our video stream.
600
-         * @param videoType 'camera' or 'screen'
601
-         */
602
-        addVideoTypeToPresence: function (videoType) {
603
-            this.presMap['videoTypeNs'] = 'http://jitsi.org/jitmeet/video';
604
-            this.presMap['videoType'] = videoType;
605
-        },
606
-        addPreziToPresence: function (url, currentSlide) {
607
-            this.presMap['prezins'] = 'http://jitsi.org/jitmeet/prezi';
608
-            this.presMap['preziurl'] = url;
609
-            this.presMap['prezicurrent'] = currentSlide;
610
-        },
611
-        removePreziFromPresence: function () {
612
-            delete this.presMap['prezins'];
613
-            delete this.presMap['preziurl'];
614
-            delete this.presMap['prezicurrent'];
615
-        },
616
-        addCurrentSlideToPresence: function (currentSlide) {
617
-            this.presMap['prezicurrent'] = currentSlide;
618
-        },
619
-        getPrezi: function (roomjid) {
620
-            return this.preziMap[roomjid];
621
-        },
622
-        addAudioInfoToPresence: function (isMuted) {
623
-            this.presMap['audions'] = 'http://jitsi.org/jitmeet/audio';
624
-            this.presMap['audiomuted'] = isMuted.toString();
625
-        },
626
-        addVideoInfoToPresence: function (isMuted) {
627
-            this.presMap['videons'] = 'http://jitsi.org/jitmeet/video';
628
-            this.presMap['videomuted'] = isMuted.toString();
629
-        },
630
-        addConnectionInfoToPresence: function (stats) {
631
-            this.presMap['statsns'] = 'http://jitsi.org/jitmeet/stats';
632
-            this.presMap['stats'] = stats;
633
-        },
634
-        findJidFromResource: function (resourceJid) {
635
-            if (resourceJid &&
636
-                resourceJid === Strophe.getResourceFromJid(this.myroomjid)) {
637
-                return this.myroomjid;
638
-            }
639
-            var peerJid = null;
640
-            Object.keys(this.members).some(function (jid) {
641
-                peerJid = jid;
642
-                return Strophe.getResourceFromJid(jid) === resourceJid;
643
-            });
644
-            return peerJid;
645
-        },
646
-        addBridgeIsDownToPresence: function () {
647
-            this.presMap['bridgeIsDown'] = true;
648
-        },
649
-        addEmailToPresence: function (email) {
650
-            this.presMap['email'] = email;
651
-        },
652
-        addUserIdToPresence: function (userId) {
653
-            this.presMap['userId'] = userId;
654
-        },
655
-        addStartMutedToPresence: function (audio, video) {
656
-            this.presMap["startMuted"] = {audio: audio, video: video};
657
-        },
658
-        isModerator: function () {
659
-            return this.role === 'moderator';
660
-        },
661
-        getMemberRole: function (peerJid) {
662
-            if (this.members[peerJid]) {
663
-                return this.members[peerJid].role;
664
-            }
665
-            return null;
666
-        },
667
-        onParticipantLeft: function (jid) {
668
-
669
-            eventEmitter.emit(XMPPEvents.MUC_MEMBER_LEFT, jid);
670
-
671
-            this.connection.jingle.terminateByJid(jid);
672
-
673
-            if (this.getPrezi(jid)) {
674
-                $(document).trigger('presentationremoved.muc',
675
-                    [jid, this.getPrezi(jid)]);
676
-            }
677
-
678
-            Moderator.onMucMemberLeft(jid);
679
-        },
680
-        parsePresence: function (from, member, pres) {
681
-            if($(pres).find(">bridgeIsDown").length > 0 && !this.bridgeIsDown) {
682
-                this.bridgeIsDown = true;
683
-                eventEmitter.emit(XMPPEvents.BRIDGE_DOWN);
684
-            }
685
-
686
-            if(member.isFocus)
687
-                return;
688
-
689
-            var displayName = !config.displayJids
690
-                ? member.displayName : Strophe.getResourceFromJid(from);
691
-
692
-            if (displayName && displayName.length > 0) {
693
-                eventEmitter.emit(XMPPEvents.DISPLAY_NAME_CHANGED, from, displayName);
694
-            }
695
-
696
-            var id = $(pres).find('>userID').text();
697
-            var email = $(pres).find('>email');
698
-            if (email.length > 0) {
699
-                id = email.text();
700
-            }
701
-
702
-            eventEmitter.emit(XMPPEvents.USER_ID_CHANGED, from, id);
703
-        }
704
-    });
705
-};
706
-

+ 0
- 341
modules/xmpp/strophe.jingle.js ファイルの表示

@@ -1,341 +0,0 @@
1
-/* jshint -W117 */
2
-/* jshint -W101 */
3
-
4
-var JingleSession = require("./JingleSessionPC");
5
-var XMPPEvents = require("../../service/xmpp/XMPPEvents");
6
-var RTCBrowserType = require("../RTC/RTCBrowserType");
7
-
8
-
9
-module.exports = function(XMPP, eventEmitter) {
10
-    Strophe.addConnectionPlugin('jingle', {
11
-        connection: null,
12
-        sessions: {},
13
-        jid2session: {},
14
-        ice_config: {iceServers: []},
15
-        pc_constraints: {},
16
-        activecall: null,
17
-        media_constraints: {
18
-            mandatory: {
19
-                'OfferToReceiveAudio': true,
20
-                'OfferToReceiveVideo': true
21
-            }
22
-            // MozDontOfferDataChannel: true when this is firefox
23
-        },
24
-        init: function (conn) {
25
-            this.connection = conn;
26
-            if (this.connection.disco) {
27
-                // http://xmpp.org/extensions/xep-0167.html#support
28
-                // http://xmpp.org/extensions/xep-0176.html#support
29
-                this.connection.disco.addFeature('urn:xmpp:jingle:1');
30
-                this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:1');
31
-                this.connection.disco.addFeature('urn:xmpp:jingle:transports:ice-udp:1');
32
-                this.connection.disco.addFeature('urn:xmpp:jingle:apps:dtls:0');
33
-                this.connection.disco.addFeature('urn:xmpp:jingle:transports:dtls-sctp:1');
34
-                this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:audio');
35
-                this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:video');
36
-
37
-                if (RTCBrowserType.isChrome() || RTCBrowserType.isOpera() ||
38
-                    RTCBrowserType.isTemasysPluginUsed()) {
39
-                    this.connection.disco.addFeature('urn:ietf:rfc:4588');
40
-                }
41
-
42
-                // this is dealt with by SDP O/A so we don't need to announce this
43
-                //this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:rtcp-fb:0'); // XEP-0293
44
-                //this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:rtp-hdrext:0'); // XEP-0294
45
-
46
-                this.connection.disco.addFeature('urn:ietf:rfc:5761'); // rtcp-mux
47
-                this.connection.disco.addFeature('urn:ietf:rfc:5888'); // a=group, e.g. bundle
48
-
49
-                //this.connection.disco.addFeature('urn:ietf:rfc:5576'); // a=ssrc
50
-            }
51
-            this.connection.addHandler(this.onJingle.bind(this), 'urn:xmpp:jingle:1', 'iq', 'set', null, null);
52
-        },
53
-        onJingle: function (iq) {
54
-            var sid = $(iq).find('jingle').attr('sid');
55
-            var action = $(iq).find('jingle').attr('action');
56
-            var fromJid = iq.getAttribute('from');
57
-            // send ack first
58
-            var ack = $iq({type: 'result',
59
-                to: fromJid,
60
-                id: iq.getAttribute('id')
61
-            });
62
-            console.log('on jingle ' + action + ' from ' + fromJid, iq);
63
-            var sess = this.sessions[sid];
64
-            if ('session-initiate' != action) {
65
-                if (sess === null) {
66
-                    ack.type = 'error';
67
-                    ack.c('error', {type: 'cancel'})
68
-                        .c('item-not-found', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up()
69
-                        .c('unknown-session', {xmlns: 'urn:xmpp:jingle:errors:1'});
70
-                    this.connection.send(ack);
71
-                    return true;
72
-                }
73
-                // local jid is not checked
74
-                if (fromJid != sess.peerjid) {
75
-                    console.warn('jid mismatch for session id', sid, fromJid, sess.peerjid);
76
-                    ack.type = 'error';
77
-                    ack.c('error', {type: 'cancel'})
78
-                        .c('item-not-found', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up()
79
-                        .c('unknown-session', {xmlns: 'urn:xmpp:jingle:errors:1'});
80
-                    this.connection.send(ack);
81
-                    return true;
82
-                }
83
-            } else if (sess !== undefined) {
84
-                // existing session with same session id
85
-                // this might be out-of-order if the sess.peerjid is the same as from
86
-                ack.type = 'error';
87
-                ack.c('error', {type: 'cancel'})
88
-                    .c('service-unavailable', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up();
89
-                console.warn('duplicate session id', sid);
90
-                this.connection.send(ack);
91
-                return true;
92
-            }
93
-            // FIXME: check for a defined action
94
-            this.connection.send(ack);
95
-            // see http://xmpp.org/extensions/xep-0166.html#concepts-session
96
-            switch (action) {
97
-                case 'session-initiate':
98
-                    console.log("(TIME) received session-initiate:\t",
99
-                                window.performance.now(), iq);
100
-                    var startMuted = $(iq).find('jingle>startmuted');
101
-                    if (startMuted && startMuted.length > 0) {
102
-                        var audioMuted = startMuted.attr("audio");
103
-                        var videoMuted = startMuted.attr("video");
104
-                        eventEmitter.emit(XMPPEvents.START_MUTED_FROM_FOCUS,
105
-                                audioMuted === "true", videoMuted === "true");
106
-                    }
107
-                    sess = new JingleSession(
108
-                        $(iq).attr('to'), $(iq).find('jingle').attr('sid'),
109
-                        this.connection, XMPP, eventEmitter);
110
-                    // configure session
111
-
112
-                    sess.media_constraints = this.media_constraints;
113
-                    sess.pc_constraints = this.pc_constraints;
114
-                    sess.ice_config = this.ice_config;
115
-
116
-                    sess.initialize(fromJid, false);
117
-                    // FIXME: setRemoteDescription should only be done when this call is to be accepted
118
-                    sess.setOffer($(iq).find('>jingle'));
119
-
120
-                    this.sessions[sess.sid] = sess;
121
-                    this.jid2session[sess.peerjid] = sess;
122
-
123
-                    // the callback should either
124
-                    // .sendAnswer and .accept
125
-                    // or .sendTerminate -- not necessarily synchronous
126
-
127
-                    // TODO: do we check activecall == null?
128
-                    this.connection.jingle.activecall = sess;
129
-
130
-                    eventEmitter.emit(XMPPEvents.CALL_INCOMING, sess);
131
-
132
-                    // TODO: check affiliation and/or role
133
-                    console.log('emuc data for', sess.peerjid,
134
-                        this.connection.emuc.members[sess.peerjid]);
135
-                    sess.sendAnswer();
136
-                    sess.accept();
137
-                    break;
138
-                case 'session-accept':
139
-                    sess.setAnswer($(iq).find('>jingle'));
140
-                    sess.accept();
141
-                    $(document).trigger('callaccepted.jingle', [sess.sid]);
142
-                    break;
143
-                case 'session-terminate':
144
-                    if (!sess) {
145
-                        break;
146
-                    }
147
-                    console.log('terminating...', sess.sid);
148
-                    sess.terminate();
149
-                    this.terminate(sess.sid);
150
-                    if ($(iq).find('>jingle>reason').length) {
151
-                        $(document).trigger('callterminated.jingle', [
152
-                            sess.sid,
153
-                            sess.peerjid,
154
-                            $(iq).find('>jingle>reason>:first')[0].tagName,
155
-                            $(iq).find('>jingle>reason>text').text()
156
-                        ]);
157
-                    } else {
158
-                        $(document).trigger('callterminated.jingle',
159
-                            [sess.sid, sess.peerjid]);
160
-                    }
161
-                    break;
162
-                case 'transport-info':
163
-                    sess.addIceCandidate($(iq).find('>jingle>content'));
164
-                    break;
165
-                case 'session-info':
166
-                    var affected;
167
-                    if ($(iq).find('>jingle>ringing[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) {
168
-                        $(document).trigger('ringing.jingle', [sess.sid]);
169
-                    } else if ($(iq).find('>jingle>mute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) {
170
-                        affected = $(iq).find('>jingle>mute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').attr('name');
171
-                        $(document).trigger('mute.jingle', [sess.sid, affected]);
172
-                    } else if ($(iq).find('>jingle>unmute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) {
173
-                        affected = $(iq).find('>jingle>unmute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').attr('name');
174
-                        $(document).trigger('unmute.jingle', [sess.sid, affected]);
175
-                    }
176
-                    break;
177
-                case 'addsource': // FIXME: proprietary, un-jingleish
178
-                case 'source-add': // FIXME: proprietary
179
-                    console.info("source-add", iq);
180
-                    sess.addSource($(iq).find('>jingle>content'));
181
-                    break;
182
-                case 'removesource': // FIXME: proprietary, un-jingleish
183
-                case 'source-remove': // FIXME: proprietary
184
-                    console.info("source-remove", iq);
185
-                    sess.removeSource($(iq).find('>jingle>content'));
186
-                    break;
187
-                default:
188
-                    console.warn('jingle action not implemented', action);
189
-                    break;
190
-            }
191
-            return true;
192
-        },
193
-        initiate: function (peerjid, myjid) { // initiate a new jinglesession to peerjid
194
-            var sess = new JingleSession(myjid || this.connection.jid,
195
-                Math.random().toString(36).substr(2, 12), // random string
196
-                this.connection, XMPP, eventEmitter);
197
-            // configure session
198
-
199
-            sess.media_constraints = this.media_constraints;
200
-            sess.pc_constraints = this.pc_constraints;
201
-            sess.ice_config = this.ice_config;
202
-
203
-            sess.initialize(peerjid, true);
204
-            this.sessions[sess.sid] = sess;
205
-            this.jid2session[sess.peerjid] = sess;
206
-            sess.sendOffer();
207
-            return sess;
208
-        },
209
-        terminate: function (sid, reason, text) { // terminate by sessionid (or all sessions)
210
-            if (sid === null || sid === undefined) {
211
-                for (sid in this.sessions) {
212
-                    if (this.sessions[sid].state != 'ended') {
213
-                        this.sessions[sid].sendTerminate(reason || (!this.sessions[sid].active()) ? 'cancel' : null, text);
214
-                        this.sessions[sid].terminate();
215
-                    }
216
-                    delete this.jid2session[this.sessions[sid].peerjid];
217
-                    delete this.sessions[sid];
218
-                }
219
-            } else if (this.sessions.hasOwnProperty(sid)) {
220
-                if (this.sessions[sid].state != 'ended') {
221
-                    this.sessions[sid].sendTerminate(reason || (!this.sessions[sid].active()) ? 'cancel' : null, text);
222
-                    this.sessions[sid].terminate();
223
-                }
224
-                delete this.jid2session[this.sessions[sid].peerjid];
225
-                delete this.sessions[sid];
226
-            }
227
-        },
228
-        // Used to terminate a session when an unavailable presence is received.
229
-        terminateByJid: function (jid) {
230
-            if (this.jid2session.hasOwnProperty(jid)) {
231
-                var sess = this.jid2session[jid];
232
-                if (sess) {
233
-                    sess.terminate();
234
-                    console.log('peer went away silently', jid);
235
-                    delete this.sessions[sess.sid];
236
-                    delete this.jid2session[jid];
237
-                    $(document).trigger('callterminated.jingle',
238
-                        [sess.sid, jid], 'gone');
239
-                }
240
-            }
241
-        },
242
-        terminateRemoteByJid: function (jid, reason) {
243
-            if (this.jid2session.hasOwnProperty(jid)) {
244
-                var sess = this.jid2session[jid];
245
-                if (sess) {
246
-                    sess.sendTerminate(reason || (!sess.active()) ? 'kick' : null);
247
-                    sess.terminate();
248
-                    console.log('terminate peer with jid', sess.sid, jid);
249
-                    delete this.sessions[sess.sid];
250
-                    delete this.jid2session[jid];
251
-                    $(document).trigger('callterminated.jingle',
252
-                        [sess.sid, jid, 'kicked']);
253
-                }
254
-            }
255
-        },
256
-        getStunAndTurnCredentials: function () {
257
-            // get stun and turn configuration from server via xep-0215
258
-            // uses time-limited credentials as described in
259
-            // http://tools.ietf.org/html/draft-uberti-behave-turn-rest-00
260
-            //
261
-            // see https://code.google.com/p/prosody-modules/source/browse/mod_turncredentials/mod_turncredentials.lua
262
-            // for a prosody module which implements this
263
-            //
264
-            // currently, this doesn't work with updateIce and therefore credentials with a long
265
-            // validity have to be fetched before creating the peerconnection
266
-            // TODO: implement refresh via updateIce as described in
267
-            //      https://code.google.com/p/webrtc/issues/detail?id=1650
268
-            var self = this;
269
-            this.connection.sendIQ(
270
-                $iq({type: 'get', to: this.connection.domain})
271
-                    .c('services', {xmlns: 'urn:xmpp:extdisco:1'}).c('service', {host: 'turn.' + this.connection.domain}),
272
-                function (res) {
273
-                    var iceservers = [];
274
-                    $(res).find('>services>service').each(function (idx, el) {
275
-                        el = $(el);
276
-                        var dict = {};
277
-                        var type = el.attr('type');
278
-                        switch (type) {
279
-                            case 'stun':
280
-                                dict.url = 'stun:' + el.attr('host');
281
-                                if (el.attr('port')) {
282
-                                    dict.url += ':' + el.attr('port');
283
-                                }
284
-                                iceservers.push(dict);
285
-                                break;
286
-                            case 'turn':
287
-                            case 'turns':
288
-                                dict.url = type + ':';
289
-                                if (el.attr('username')) { // https://code.google.com/p/webrtc/issues/detail?id=1508
290
-                                    if (navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./) && parseInt(navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2], 10) < 28) {
291
-                                        dict.url += el.attr('username') + '@';
292
-                                    } else {
293
-                                        dict.username = el.attr('username'); // only works in M28
294
-                                    }
295
-                                }
296
-                                dict.url += el.attr('host');
297
-                                if (el.attr('port') && el.attr('port') != '3478') {
298
-                                    dict.url += ':' + el.attr('port');
299
-                                }
300
-                                if (el.attr('transport') && el.attr('transport') != 'udp') {
301
-                                    dict.url += '?transport=' + el.attr('transport');
302
-                                }
303
-                                if (el.attr('password')) {
304
-                                    dict.credential = el.attr('password');
305
-                                }
306
-                                iceservers.push(dict);
307
-                                break;
308
-                        }
309
-                    });
310
-                    self.ice_config.iceServers = iceservers;
311
-                },
312
-                function (err) {
313
-                    console.warn('getting turn credentials failed', err);
314
-                    console.warn('is mod_turncredentials or similar installed?');
315
-                }
316
-            );
317
-            // implement push?
318
-        },
319
-
320
-        /**
321
-         * Returns the data saved in 'updateLog' in a format to be logged.
322
-         */
323
-        getLog: function () {
324
-            var data = {};
325
-            var self = this;
326
-            Object.keys(this.sessions).forEach(function (sid) {
327
-                var session = self.sessions[sid];
328
-                if (session.peerconnection && session.peerconnection.updateLog) {
329
-                    // FIXME: should probably be a .dump call
330
-                    data["jingle_" + session.sid] = {
331
-                        updateLog: session.peerconnection.updateLog,
332
-                        stats: session.peerconnection.stats,
333
-                        url: window.location.href
334
-                    };
335
-                }
336
-            });
337
-            return data;
338
-        }
339
-    });
340
-};
341
-

+ 0
- 20
modules/xmpp/strophe.logger.js ファイルの表示

@@ -1,20 +0,0 @@
1
-/* global Strophe */
2
-module.exports = function () {
3
-
4
-    Strophe.addConnectionPlugin('logger', {
5
-        // logs raw stanzas and makes them available for download as JSON
6
-        connection: null,
7
-        log: [],
8
-        init: function (conn) {
9
-            this.connection = conn;
10
-            this.connection.rawInput = this.log_incoming.bind(this);
11
-            this.connection.rawOutput = this.log_outgoing.bind(this);
12
-        },
13
-        log_incoming: function (stanza) {
14
-            this.log.push([new Date().getTime(), 'incoming', stanza]);
15
-        },
16
-        log_outgoing: function (stanza) {
17
-            this.log.push([new Date().getTime(), 'outgoing', stanza]);
18
-        }
19
-    });
20
-};

+ 0
- 60
modules/xmpp/strophe.moderate.js ファイルの表示

@@ -1,60 +0,0 @@
1
-/* global $, $iq, config, connection, focusMucJid, forceMuted, Strophe */
2
-/**
3
- * Moderate connection plugin.
4
- */
5
-var XMPPEvents = require("../../service/xmpp/XMPPEvents");
6
-
7
-module.exports = function (XMPP, eventEmitter) {
8
-    Strophe.addConnectionPlugin('moderate', {
9
-        connection: null,
10
-        init: function (conn) {
11
-            this.connection = conn;
12
-
13
-            this.connection.addHandler(this.onMute.bind(this),
14
-                'http://jitsi.org/jitmeet/audio',
15
-                'iq',
16
-                'set',
17
-                null,
18
-                null);
19
-        },
20
-        setMute: function (jid, mute) {
21
-            console.info("set mute", mute);
22
-            var iqToFocus =
23
-                $iq({to: this.connection.emuc.focusMucJid, type: 'set'})
24
-                .c('mute', {
25
-                    xmlns: 'http://jitsi.org/jitmeet/audio',
26
-                    jid: jid
27
-                })
28
-                .t(mute.toString())
29
-                .up();
30
-
31
-            this.connection.sendIQ(
32
-                iqToFocus,
33
-                function (result) {
34
-                    console.log('set mute', result);
35
-                },
36
-                function (error) {
37
-                    console.log('set mute error', error);
38
-                });
39
-        },
40
-        onMute: function (iq) {
41
-            var from = iq.getAttribute('from');
42
-            if (from !== this.connection.emuc.focusMucJid) {
43
-                console.warn("Ignored mute from non focus peer");
44
-                return false;
45
-            }
46
-            var mute = $(iq).find('mute');
47
-            if (mute.length) {
48
-                var doMuteAudio = mute.text() === "true";
49
-                eventEmitter.emit(XMPPEvents.AUDIO_MUTED_BY_FOCUS, doMuteAudio);
50
-                XMPP.forceMuted = doMuteAudio;
51
-            }
52
-            return true;
53
-        },
54
-        eject: function (jid) {
55
-            // We're not the focus, so can't terminate
56
-            //connection.jingle.terminateRemoteByJid(jid, 'kick');
57
-            this.connection.emuc.kick(jid);
58
-        }
59
-    });
60
-};

+ 0
- 121
modules/xmpp/strophe.ping.js ファイルの表示

@@ -1,121 +0,0 @@
1
-/* global $, $iq, Strophe */
2
-
3
-var XMPPEvents = require("../../service/xmpp/XMPPEvents");
4
-
5
-/**
6
- * Ping every 20 sec
7
- */
8
-var PING_INTERVAL = 20000;
9
-
10
-/**
11
- * Ping timeout error after 15 sec of waiting.
12
- */
13
-var PING_TIMEOUT = 15000;
14
-
15
-/**
16
- * Will close the connection after 3 consecutive ping errors.
17
- */
18
-var PING_THRESHOLD = 3;
19
-
20
-/**
21
- * XEP-0199 ping plugin.
22
- *
23
- * Registers "urn:xmpp:ping" namespace under Strophe.NS.PING.
24
- */
25
-module.exports = function (XMPP, eventEmitter) {
26
-    Strophe.addConnectionPlugin('ping', {
27
-
28
-        connection: null,
29
-
30
-        failedPings: 0,
31
-
32
-        /**
33
-         * Initializes the plugin. Method called by Strophe.
34
-         * @param connection Strophe connection instance.
35
-         */
36
-        init: function (connection) {
37
-            this.connection = connection;
38
-            Strophe.addNamespace('PING', "urn:xmpp:ping");
39
-        },
40
-
41
-        /**
42
-         * Sends "ping" to given <tt>jid</tt>
43
-         * @param jid the JID to which ping request will be sent.
44
-         * @param success callback called on success.
45
-         * @param error callback called on error.
46
-         * @param timeout ms how long are we going to wait for the response. On
47
-         *        timeout <tt>error<//t> callback is called with undefined error
48
-         *        argument.
49
-         */
50
-        ping: function (jid, success, error, timeout) {
51
-            var iq = $iq({type: 'get', to: jid});
52
-            iq.c('ping', {xmlns: Strophe.NS.PING});
53
-            this.connection.sendIQ(iq, success, error, timeout);
54
-        },
55
-
56
-        /**
57
-         * Checks if given <tt>jid</tt> has XEP-0199 ping support.
58
-         * @param jid the JID to be checked for ping support.
59
-         * @param callback function with boolean argument which will be
60
-         * <tt>true</tt> if XEP-0199 ping is supported by given <tt>jid</tt>
61
-         */
62
-        hasPingSupport: function (jid, callback) {
63
-            this.connection.disco.info(
64
-                jid, null,
65
-                function (result) {
66
-                    var ping = $(result).find('>>feature[var="urn:xmpp:ping"]');
67
-                    callback(ping.length > 0);
68
-                },
69
-                function (error) {
70
-                    console.error("Ping feature discovery error", error);
71
-                    callback(false);
72
-                }
73
-            );
74
-        },
75
-
76
-        /**
77
-         * Starts to send ping in given interval to specified remote JID.
78
-         * This plugin supports only one such task and <tt>stopInterval</tt>
79
-         * must be called before starting a new one.
80
-         * @param remoteJid remote JID to which ping requests will be sent to.
81
-         * @param interval task interval in ms.
82
-         */
83
-        startInterval: function (remoteJid, interval) {
84
-            if (this.intervalId) {
85
-                console.error("Ping task scheduled already");
86
-                return;
87
-            }
88
-            if (!interval)
89
-                interval = PING_INTERVAL;
90
-            var self = this;
91
-            this.intervalId = window.setInterval(function () {
92
-                self.ping(remoteJid,
93
-                function (result) {
94
-                    // Ping OK
95
-                    self.failedPings = 0;
96
-                },
97
-                function (error) {
98
-                    self.failedPings += 1;
99
-                    console.error(
100
-                        "Ping " + (error ? "error" : "timeout"), error);
101
-                    if (self.failedPings >= PING_THRESHOLD) {
102
-                        self.connection.disconnect();
103
-                    }
104
-                }, PING_TIMEOUT);
105
-            }, interval);
106
-            console.info("XMPP pings will be sent every " + interval + " ms");
107
-        },
108
-
109
-        /**
110
-         * Stops current "ping"  interval task.
111
-         */
112
-        stopInterval: function () {
113
-            if (this.intervalId) {
114
-                window.clearInterval(this.intervalId);
115
-                this.intervalId = null;
116
-                this.failedPings = 0;
117
-                console.info("Ping interval cleared");
118
-            }
119
-        }
120
-    });
121
-};

+ 0
- 96
modules/xmpp/strophe.rayo.js ファイルの表示

@@ -1,96 +0,0 @@
1
-/* jshint -W117 */
2
-module.exports = function() {
3
-    Strophe.addConnectionPlugin('rayo',
4
-        {
5
-            RAYO_XMLNS: 'urn:xmpp:rayo:1',
6
-            connection: null,
7
-            init: function (conn) {
8
-                this.connection = conn;
9
-                if (this.connection.disco) {
10
-                    this.connection.disco.addFeature('urn:xmpp:rayo:client:1');
11
-                }
12
-
13
-                this.connection.addHandler(
14
-                    this.onRayo.bind(this), this.RAYO_XMLNS, 'iq', 'set',
15
-                    null, null);
16
-            },
17
-            onRayo: function (iq) {
18
-                console.info("Rayo IQ", iq);
19
-            },
20
-            dial: function (to, from, roomName, roomPass) {
21
-                var self = this;
22
-                var req = $iq(
23
-                    {
24
-                        type: 'set',
25
-                        to: this.connection.emuc.focusMucJid
26
-                    }
27
-                );
28
-                req.c('dial',
29
-                    {
30
-                        xmlns: this.RAYO_XMLNS,
31
-                        to: to,
32
-                        from: from
33
-                    });
34
-                req.c('header',
35
-                    {
36
-                        name: 'JvbRoomName',
37
-                        value: roomName
38
-                    }).up();
39
-
40
-                if (roomPass !== null && roomPass.length) {
41
-
42
-                    req.c('header',
43
-                        {
44
-                            name: 'JvbRoomPassword',
45
-                            value: roomPass
46
-                        }).up();
47
-                }
48
-
49
-                this.connection.sendIQ(
50
-                    req,
51
-                    function (result) {
52
-                        console.info('Dial result ', result);
53
-
54
-                        var resource = $(result).find('ref').attr('uri');
55
-                        self.call_resource = resource.substr('xmpp:'.length);
56
-                        console.info(
57
-                            "Received call resource: " + self.call_resource);
58
-                    },
59
-                    function (error) {
60
-                        console.info('Dial error ', error);
61
-                    }
62
-                );
63
-            },
64
-            hang_up: function () {
65
-                if (!this.call_resource) {
66
-                    console.warn("No call in progress");
67
-                    return;
68
-                }
69
-
70
-                var self = this;
71
-                var req = $iq(
72
-                    {
73
-                        type: 'set',
74
-                        to: this.call_resource
75
-                    }
76
-                );
77
-                req.c('hangup',
78
-                    {
79
-                        xmlns: this.RAYO_XMLNS
80
-                    });
81
-
82
-                this.connection.sendIQ(
83
-                    req,
84
-                    function (result) {
85
-                        console.info('Hangup result ', result);
86
-                        self.call_resource = null;
87
-                    },
88
-                    function (error) {
89
-                        console.info('Hangup error ', error);
90
-                        self.call_resource = null;
91
-                    }
92
-                );
93
-            }
94
-        }
95
-    );
96
-};

+ 0
- 43
modules/xmpp/strophe.util.js ファイルの表示

@@ -1,43 +0,0 @@
1
-/* global Strophe */
2
-/**
3
- * Strophe logger implementation. Logs from level WARN and above.
4
- */
5
-module.exports = function () {
6
-
7
-    Strophe.log = function (level, msg) {
8
-        switch (level) {
9
-            case Strophe.LogLevel.WARN:
10
-                console.warn("Strophe: " + msg);
11
-                break;
12
-            case Strophe.LogLevel.ERROR:
13
-            case Strophe.LogLevel.FATAL:
14
-                console.error("Strophe: " + msg);
15
-                break;
16
-        }
17
-    };
18
-
19
-    Strophe.getStatusString = function (status) {
20
-        switch (status) {
21
-            case Strophe.Status.ERROR:
22
-                return "ERROR";
23
-            case Strophe.Status.CONNECTING:
24
-                return "CONNECTING";
25
-            case Strophe.Status.CONNFAIL:
26
-                return "CONNFAIL";
27
-            case Strophe.Status.AUTHENTICATING:
28
-                return "AUTHENTICATING";
29
-            case Strophe.Status.AUTHFAIL:
30
-                return "AUTHFAIL";
31
-            case Strophe.Status.CONNECTED:
32
-                return "CONNECTED";
33
-            case Strophe.Status.DISCONNECTED:
34
-                return "DISCONNECTED";
35
-            case Strophe.Status.DISCONNECTING:
36
-                return "DISCONNECTING";
37
-            case Strophe.Status.ATTACHED:
38
-                return "ATTACHED";
39
-            default:
40
-                return "unknown";
41
-        }
42
-    };
43
-};

+ 0
- 616
modules/xmpp/xmpp.js ファイルの表示

@@ -1,616 +0,0 @@
1
-/* global $, APP, config, Strophe, Base64, $msg */
2
-/* jshint -W101 */
3
-var Moderator = require("./moderator");
4
-var EventEmitter = require("events");
5
-var Recording = require("./recording");
6
-var SDP = require("./SDP");
7
-var SDPUtil = require("./SDPUtil");
8
-var Settings = require("../settings/Settings");
9
-var Pako = require("pako");
10
-var StreamEventTypes = require("../../service/RTC/StreamEventTypes");
11
-var RTCEvents = require("../../service/RTC/RTCEvents");
12
-var XMPPEvents = require("../../service/xmpp/XMPPEvents");
13
-var retry = require('retry');
14
-var RandomUtil = require("../util/RandomUtil");
15
-
16
-var eventEmitter = new EventEmitter();
17
-var connection = null;
18
-var authenticatedUser = false;
19
-
20
-/**
21
- * Utility method that generates user name based on random hex values.
22
- * Eg. 12345678-1234-1234-12345678
23
- * @returns {string}
24
- */
25
-function generateUserName() {
26
-    return RandomUtil.randomHexString(8) + "-" + RandomUtil.randomHexString(4)
27
-        + "-" + RandomUtil.randomHexString(4) + "-"
28
-        + RandomUtil.randomHexString(8);
29
-}
30
-
31
-function connect(jid, password) {
32
-
33
-    var faultTolerantConnect = retry.operation({
34
-        retries: 3
35
-    });
36
-
37
-    // fault tolerant connect
38
-    faultTolerantConnect.attempt(function () {
39
-
40
-        connection = XMPP.createConnection();
41
-        Moderator.setConnection(connection);
42
-
43
-        connection.jingle.pc_constraints = APP.RTC.getPCConstraints();
44
-        if (config.useIPv6) {
45
-            // https://code.google.com/p/webrtc/issues/detail?id=2828
46
-            if (!connection.jingle.pc_constraints.optional)
47
-                connection.jingle.pc_constraints.optional = [];
48
-            connection.jingle.pc_constraints.optional.push({googIPv6: true});
49
-        }
50
-
51
-        // Include user info in MUC presence
52
-        var settings = Settings.getSettings();
53
-        if (settings.email) {
54
-            connection.emuc.addEmailToPresence(settings.email);
55
-        }
56
-        if (settings.uid) {
57
-            connection.emuc.addUserIdToPresence(settings.uid);
58
-        }
59
-        if (settings.displayName) {
60
-            connection.emuc.addDisplayNameToPresence(settings.displayName);
61
-        }
62
-
63
-
64
-        // connection.connect() starts the connection process.
65
-        //
66
-        // As the connection process proceeds, the user supplied callback will
67
-        // be triggered multiple times with status updates. The callback should
68
-        // take two arguments - the status code and the error condition.
69
-        //
70
-        // The status code will be one of the values in the Strophe.Status
71
-        // constants. The error condition will be one of the conditions defined
72
-        // in RFC 3920 or the condition ‘strophe-parsererror’.
73
-        //
74
-        // The Parameters wait, hold and route are optional and only relevant
75
-        // for BOSH connections. Please see XEP 124 for a more detailed
76
-        // explanation of the optional parameters.
77
-        //
78
-        // Connection status constants for use by the connection handler
79
-        // callback.
80
-        //
81
-        //  Status.ERROR - An error has occurred (websockets specific)
82
-        //  Status.CONNECTING - The connection is currently being made
83
-        //  Status.CONNFAIL - The connection attempt failed
84
-        //  Status.AUTHENTICATING - The connection is authenticating
85
-        //  Status.AUTHFAIL - The authentication attempt failed
86
-        //  Status.CONNECTED - The connection has succeeded
87
-        //  Status.DISCONNECTED - The connection has been terminated
88
-        //  Status.DISCONNECTING - The connection is currently being terminated
89
-        //  Status.ATTACHED - The connection has been attached
90
-
91
-        var anonymousConnectionFailed = false;
92
-        var connectionFailed = false;
93
-        var lastErrorMsg;
94
-        connection.connect(jid, password, function (status, msg) {
95
-            console.log("(TIME) Strophe " + Strophe.getStatusString(status) +
96
-                (msg ? "[" + msg + "]" : "") +
97
-                "\t:" + window.performance.now());
98
-            if (status === Strophe.Status.CONNECTED) {
99
-                if (config.useStunTurn) {
100
-                    connection.jingle.getStunAndTurnCredentials();
101
-                }
102
-
103
-                console.info("My Jabber ID: " + connection.jid);
104
-
105
-                // Schedule ping ?
106
-                var pingJid = connection.domain;
107
-                connection.ping.hasPingSupport(
108
-                    pingJid,
109
-                    function (hasPing) {
110
-                        if (hasPing)
111
-                            connection.ping.startInterval(pingJid);
112
-                        else
113
-                            console.warn("Ping NOT supported by " + pingJid);
114
-                    }
115
-                );
116
-
117
-                if (password)
118
-                    authenticatedUser = true;
119
-                maybeDoJoin();
120
-            } else if (status === Strophe.Status.CONNFAIL) {
121
-                if (msg === 'x-strophe-bad-non-anon-jid') {
122
-                    anonymousConnectionFailed = true;
123
-                } else {
124
-                    connectionFailed = true;
125
-                }
126
-                lastErrorMsg = msg;
127
-            } else if (status === Strophe.Status.DISCONNECTED) {
128
-                // Stop ping interval
129
-                connection.ping.stopInterval();
130
-                if (anonymousConnectionFailed) {
131
-                    // prompt user for username and password
132
-                    XMPP.promptLogin();
133
-                } else {
134
-
135
-                    // Strophe already has built-in HTTP/BOSH error handling and
136
-                    // request retry logic. Requests are resent automatically
137
-                    // until their error count reaches 5. Strophe.js disconnects
138
-                    // if the error count is > 5. We are not replicating this
139
-                    // here.
140
-                    //
141
-                    // The "problem" is that failed HTTP/BOSH requests don't
142
-                    // trigger a callback with a status update, so when a
143
-                    // callback with status Strophe.Status.DISCONNECTED arrives,
144
-                    // we can't be sure if it's a graceful disconnect or if it's
145
-                    // triggered by some HTTP/BOSH error.
146
-                    //
147
-                    // But that's a minor issue in Jitsi Meet as we never
148
-                    // disconnect anyway, not even when the user closes the
149
-                    // browser window (which is kind of wrong, but the point is
150
-                    // that we should never ever get disconnected).
151
-                    //
152
-                    // On the other hand, failed connections due to XMPP layer
153
-                    // errors, trigger a callback with status Strophe.Status.CONNFAIL.
154
-                    //
155
-                    // Here we implement retry logic for failed connections due
156
-                    // to XMPP layer errors and we display an error to the user
157
-                    // if we get disconnected from the XMPP server permanently.
158
-
159
-                    // If the connection failed, retry.
160
-                    if (connectionFailed &&
161
-                        faultTolerantConnect.retry("connection-failed")) {
162
-                        return;
163
-                    }
164
-
165
-                    // If we failed to connect to the XMPP server, fire an event
166
-                    // to let all the interested module now about it.
167
-                    eventEmitter.emit(XMPPEvents.CONNECTION_FAILED,
168
-                        msg ? msg : lastErrorMsg);
169
-                }
170
-            } else if (status === Strophe.Status.AUTHFAIL) {
171
-                // wrong password or username, prompt user
172
-                XMPP.promptLogin();
173
-
174
-            }
175
-        });
176
-    });
177
-}
178
-
179
-
180
-function maybeDoJoin() {
181
-    if (connection && connection.connected &&
182
-        Strophe.getResourceFromJid(connection.jid) &&
183
-        (APP.RTC.localAudio || APP.RTC.localVideo)) {
184
-        // .connected is true while connecting?
185
-        doJoin();
186
-    }
187
-}
188
-
189
-function doJoin() {
190
-    eventEmitter.emit(XMPPEvents.READY_TO_JOIN);
191
-}
192
-
193
-function initStrophePlugins()
194
-{
195
-    require("./strophe.emuc")(XMPP, eventEmitter);
196
-    require("./strophe.jingle")(XMPP, eventEmitter);
197
-    require("./strophe.moderate")(XMPP, eventEmitter);
198
-    require("./strophe.util")();
199
-    require("./strophe.ping")(XMPP, eventEmitter);
200
-    require("./strophe.rayo")();
201
-    require("./strophe.logger")();
202
-}
203
-
204
-/**
205
- * If given <tt>localStream</tt> is video one this method will advertise it's
206
- * video type in MUC presence.
207
- * @param localStream new or modified <tt>LocalStream</tt>.
208
- */
209
-function broadcastLocalVideoType(localStream) {
210
-    if (localStream.videoType)
211
-        XMPP.addToPresence('videoType', localStream.videoType);
212
-}
213
-
214
-function registerListeners() {
215
-    APP.RTC.addStreamListener(
216
-        function (localStream) {
217
-            maybeDoJoin();
218
-            broadcastLocalVideoType(localStream);
219
-        },
220
-        StreamEventTypes.EVENT_TYPE_LOCAL_CREATED
221
-    );
222
-    APP.RTC.addStreamListener(
223
-        broadcastLocalVideoType,
224
-        StreamEventTypes.EVENT_TYPE_LOCAL_CHANGED
225
-    );
226
-    APP.RTC.addListener(RTCEvents.AVAILABLE_DEVICES_CHANGED, function (devices) {
227
-        XMPP.addToPresence("devices", devices);
228
-    });
229
-}
230
-
231
-var unload = (function () {
232
-    var unloaded = false;
233
-
234
-    return function () {
235
-        if (unloaded) { return; }
236
-        unloaded = true;
237
-
238
-        if (connection && connection.connected) {
239
-            // ensure signout
240
-            $.ajax({
241
-                type: 'POST',
242
-                url: config.bosh,
243
-                async: false,
244
-                cache: false,
245
-                contentType: 'application/xml',
246
-                data: "<body rid='" +
247
-                    (connection.rid || connection._proto.rid) +
248
-                    "' xmlns='http://jabber.org/protocol/httpbind' sid='" +
249
-                    (connection.sid || connection._proto.sid)  +
250
-                    "' type='terminate'>" +
251
-                "<presence xmlns='jabber:client' type='unavailable'/>" +
252
-                "</body>",
253
-                success: function (data) {
254
-                    console.log('signed out');
255
-                    console.log(data);
256
-                },
257
-                error: function (XMLHttpRequest, textStatus, errorThrown) {
258
-                    console.log('signout error',
259
-                        textStatus + ' (' + errorThrown + ')');
260
-                }
261
-            });
262
-        }
263
-        XMPP.disposeConference(true);
264
-    };
265
-})();
266
-
267
-function setupEvents() {
268
-    // In recent versions of FF the 'beforeunload' event is not fired when the
269
-    // window or the tab is closed. It is only fired when we leave the page
270
-    // (change URL). If this participant doesn't unload properly, then it
271
-    // becomes a ghost for the rest of the participants that stay in the
272
-    // conference. Thankfully handling the 'unload' event in addition to the
273
-    // 'beforeunload' event seems to guarantee the execution of the 'unload'
274
-    // method at least once.
275
-    //
276
-    // The 'unload' method can safely be run multiple times, it will actually do
277
-    // something only the first time that it's run, so we're don't have to worry
278
-    // about browsers that fire both events.
279
-
280
-    $(window).bind('beforeunload', unload);
281
-    $(window).bind('unload', unload);
282
-}
283
-
284
-var XMPP = {
285
-    getConnection: function(){ return connection; },
286
-    sessionTerminated: false,
287
-
288
-    /**
289
-     * XMPP connection status
290
-     */
291
-    Status: Strophe.Status,
292
-
293
-    /**
294
-     * Remembers if we were muted by the focus.
295
-     * @type {boolean}
296
-     */
297
-    forceMuted: false,
298
-    start: function () {
299
-        setupEvents();
300
-        initStrophePlugins();
301
-        registerListeners();
302
-        Moderator.init(this, eventEmitter);
303
-        Recording.init();
304
-        var configDomain = config.hosts.anonymousdomain || config.hosts.domain;
305
-        // Force authenticated domain if room is appended with '?login=true'
306
-        if (config.hosts.anonymousdomain &&
307
-            window.location.search.indexOf("login=true") !== -1) {
308
-            configDomain = config.hosts.domain;
309
-        }
310
-        var jid = configDomain || window.location.hostname;
311
-        var password = null;
312
-        if (config.token) {
313
-            password = config.token;
314
-            if (config.id) {
315
-                jid = config.id + "@" + jid;
316
-            } else {
317
-                jid = generateUserName() + "@" + jid;
318
-            }
319
-        }
320
-        connect(jid, password);
321
-    },
322
-    createConnection: function () {
323
-        var bosh = config.bosh || '/http-bind';
324
-        // adds the room name used to the bosh connection
325
-        return new Strophe.Connection(bosh + '?room=' + APP.UI.getRoomNode());
326
-    },
327
-    getStatusString: function (status) {
328
-        return Strophe.getStatusString(status);
329
-    },
330
-    promptLogin: function () {
331
-        eventEmitter.emit(XMPPEvents.PROMPT_FOR_LOGIN, connect);
332
-    },
333
-    joinRoom: function(roomName, useNicks, nick) {
334
-        var roomjid = roomName;
335
-
336
-        if (useNicks) {
337
-            if (nick) {
338
-                roomjid += '/' + nick;
339
-            } else {
340
-                roomjid += '/' + Strophe.getNodeFromJid(connection.jid);
341
-            }
342
-        } else {
343
-            var tmpJid = Strophe.getNodeFromJid(connection.jid);
344
-
345
-            if(!authenticatedUser)
346
-                tmpJid = tmpJid.substr(0, 8);
347
-
348
-            roomjid += '/' + tmpJid;
349
-        }
350
-        connection.emuc.doJoin(roomjid);
351
-    },
352
-    myJid: function () {
353
-        if(!connection)
354
-            return null;
355
-        return connection.emuc.myroomjid;
356
-    },
357
-    myResource: function () {
358
-        if(!connection || ! connection.emuc.myroomjid)
359
-            return null;
360
-        return Strophe.getResourceFromJid(connection.emuc.myroomjid);
361
-    },
362
-    getLastPresence: function (from) {
363
-        if(!connection)
364
-            return null;
365
-        return connection.emuc.lastPresenceMap[from];
366
-    },
367
-    disposeConference: function (onUnload) {
368
-        var handler = connection.jingle.activecall;
369
-        if (handler && handler.peerconnection) {
370
-            // FIXME: probably removing streams is not required and close() should
371
-            // be enough
372
-            if (APP.RTC.localAudio) {
373
-                handler.peerconnection.removeStream(
374
-                    APP.RTC.localAudio.getOriginalStream(), onUnload);
375
-            }
376
-            if (APP.RTC.localVideo) {
377
-                handler.peerconnection.removeStream(
378
-                    APP.RTC.localVideo.getOriginalStream(), onUnload);
379
-            }
380
-            handler.peerconnection.close();
381
-        }
382
-        eventEmitter.emit(XMPPEvents.DISPOSE_CONFERENCE, onUnload);
383
-        connection.jingle.activecall = null;
384
-        if (!onUnload) {
385
-            this.sessionTerminated = true;
386
-            connection.emuc.doLeave();
387
-        }
388
-    },
389
-    addListener: function(type, listener) {
390
-        eventEmitter.on(type, listener);
391
-    },
392
-    removeListener: function (type, listener) {
393
-        eventEmitter.removeListener(type, listener);
394
-    },
395
-    allocateConferenceFocus: function(roomName, callback) {
396
-        Moderator.allocateConferenceFocus(roomName, callback);
397
-    },
398
-    getLoginUrl: function (roomName, callback) {
399
-        Moderator.getLoginUrl(roomName, callback);
400
-    },
401
-    getPopupLoginUrl: function (roomName, callback) {
402
-        Moderator.getPopupLoginUrl(roomName, callback);
403
-    },
404
-    isModerator: function () {
405
-        return Moderator.isModerator();
406
-    },
407
-    isSipGatewayEnabled: function () {
408
-        return Moderator.isSipGatewayEnabled();
409
-    },
410
-    isExternalAuthEnabled: function () {
411
-        return Moderator.isExternalAuthEnabled();
412
-    },
413
-    isConferenceInProgress: function () {
414
-        return connection && connection.jingle.activecall &&
415
-            connection.jingle.activecall.peerconnection;
416
-    },
417
-    switchStreams: function (stream, oldStream, callback, isAudio) {
418
-        if (this.isConferenceInProgress()) {
419
-            // FIXME: will block switchInProgress on true value in case of exception
420
-            connection.jingle.activecall.switchStreams(stream, oldStream, callback, isAudio);
421
-        } else {
422
-            // We are done immediately
423
-            console.warn("No conference handler or conference not started yet");
424
-            callback();
425
-        }
426
-    },
427
-    sendVideoInfoPresence: function (mute) {
428
-        if(!connection)
429
-            return;
430
-        connection.emuc.addVideoInfoToPresence(mute);
431
-        connection.emuc.sendPresence();
432
-    },
433
-    setVideoMute: function (mute, callback, options) {
434
-        if(!connection)
435
-            return;
436
-        var self = this;
437
-        var localCallback = function (mute) {
438
-            self.sendVideoInfoPresence(mute);
439
-            return callback(mute);
440
-        };
441
-
442
-        if(connection.jingle.activecall)
443
-        {
444
-            connection.jingle.activecall.setVideoMute(
445
-                mute, localCallback, options);
446
-        }
447
-        else {
448
-            localCallback(mute);
449
-        }
450
-
451
-    },
452
-    setAudioMute: function (mute, callback) {
453
-        if (!(connection && APP.RTC.localAudio)) {
454
-            return false;
455
-        }
456
-
457
-        if (this.forceMuted && !mute) {
458
-            console.info("Asking focus for unmute");
459
-            connection.moderate.setMute(connection.emuc.myroomjid, mute);
460
-            // FIXME: wait for result before resetting muted status
461
-            this.forceMuted = false;
462
-        }
463
-
464
-        if (mute == APP.RTC.localAudio.isMuted()) {
465
-            // Nothing to do
466
-            return true;
467
-        }
468
-
469
-        APP.RTC.localAudio.setMute(mute);
470
-        this.sendAudioInfoPresence(mute, callback);
471
-        return true;
472
-    },
473
-    sendAudioInfoPresence: function(mute, callback) {
474
-        if(connection) {
475
-            connection.emuc.addAudioInfoToPresence(mute);
476
-            connection.emuc.sendPresence();
477
-        }
478
-        callback();
479
-        return true;
480
-    },
481
-    toggleRecording: function (tokenEmptyCallback,
482
-                               recordingStateChangeCallback) {
483
-        Recording.toggleRecording(tokenEmptyCallback,
484
-            recordingStateChangeCallback, connection);
485
-    },
486
-    addToPresence: function (name, value, dontSend) {
487
-        switch (name) {
488
-            case "displayName":
489
-                connection.emuc.addDisplayNameToPresence(value);
490
-                break;
491
-            case "prezi":
492
-                connection.emuc.addPreziToPresence(value, 0);
493
-                break;
494
-            case "preziSlide":
495
-                connection.emuc.addCurrentSlideToPresence(value);
496
-                break;
497
-            case "connectionQuality":
498
-                connection.emuc.addConnectionInfoToPresence(value);
499
-                break;
500
-            case "email":
501
-                connection.emuc.addEmailToPresence(value);
502
-                break;
503
-            case "devices":
504
-                connection.emuc.addDevicesToPresence(value);
505
-                break;
506
-            case "videoType":
507
-                connection.emuc.addVideoTypeToPresence(value);
508
-                break;
509
-            case "startMuted":
510
-                if(!Moderator.isModerator())
511
-                    return;
512
-                connection.emuc.addStartMutedToPresence(value[0],
513
-                    value[1]);
514
-                break;
515
-            default :
516
-                console.log("Unknown tag for presence: " + name);
517
-                return;
518
-        }
519
-        if (!dontSend)
520
-            connection.emuc.sendPresence();
521
-    },
522
-    /**
523
-     * Sends 'data' as a log message to the focus. Returns true iff a message
524
-     * was sent.
525
-     * @param data
526
-     * @returns {boolean} true iff a message was sent.
527
-     */
528
-    sendLogs: function (data) {
529
-        if(!connection.emuc.focusMucJid)
530
-            return false;
531
-
532
-        var deflate = true;
533
-
534
-        var content = JSON.stringify(data);
535
-        if (deflate) {
536
-            content = String.fromCharCode.apply(null, Pako.deflateRaw(content));
537
-        }
538
-        content = Base64.encode(content);
539
-        // XEP-0337-ish
540
-        var message = $msg({to: connection.emuc.focusMucJid, type: 'normal'});
541
-        message.c('log', { xmlns: 'urn:xmpp:eventlog',
542
-            id: 'PeerConnectionStats'});
543
-        message.c('message').t(content).up();
544
-        if (deflate) {
545
-            message.c('tag', {name: "deflated", value: "true"}).up();
546
-        }
547
-        message.up();
548
-
549
-        connection.send(message);
550
-        return true;
551
-    },
552
-    // Gets the logs from strophe.jingle.
553
-    getJingleLog: function () {
554
-        return connection.jingle ? connection.jingle.getLog() : {};
555
-    },
556
-    // Gets the logs from strophe.
557
-    getXmppLog: function () {
558
-        return connection.logger ? connection.logger.log : null;
559
-    },
560
-    getPrezi: function () {
561
-        return connection.emuc.getPrezi(this.myJid());
562
-    },
563
-    removePreziFromPresence: function () {
564
-        connection.emuc.removePreziFromPresence();
565
-        connection.emuc.sendPresence();
566
-    },
567
-    sendChatMessage: function (message, nickname) {
568
-        connection.emuc.sendMessage(message, nickname);
569
-    },
570
-    setSubject: function (topic) {
571
-        connection.emuc.setSubject(topic);
572
-    },
573
-    lockRoom: function (key, onSuccess, onError, onNotSupported) {
574
-        connection.emuc.lockRoom(key, onSuccess, onError, onNotSupported);
575
-    },
576
-    dial: function (to, from, roomName,roomPass) {
577
-        connection.rayo.dial(to, from, roomName,roomPass);
578
-    },
579
-    setMute: function (jid, mute) {
580
-        connection.moderate.setMute(jid, mute);
581
-    },
582
-    eject: function (jid) {
583
-        connection.moderate.eject(jid);
584
-    },
585
-    logout: function (callback) {
586
-        Moderator.logout(callback);
587
-    },
588
-    findJidFromResource: function (resource) {
589
-        return connection.emuc.findJidFromResource(resource);
590
-    },
591
-    getMembers: function () {
592
-        return connection.emuc.members;
593
-    },
594
-    getJidFromSSRC: function (ssrc) {
595
-        if (!this.isConferenceInProgress())
596
-            return null;
597
-        return connection.jingle.activecall.getSsrcOwner(ssrc);
598
-    },
599
-    // Returns true iff we have joined the MUC.
600
-    isMUCJoined: function () {
601
-        return connection === null ? false : connection.emuc.joined;
602
-    },
603
-    getSessions: function () {
604
-        return connection.jingle.sessions;
605
-    },
606
-    removeStream: function (stream) {
607
-        if (!this.isConferenceInProgress())
608
-            return;
609
-        connection.jingle.activecall.peerconnection.removeStream(stream);
610
-    },
611
-    filter_special_chars: function (text) {
612
-        return SDPUtil.filter_special_chars(text);
613
-    }
614
-};
615
-
616
-module.exports = XMPP;

読み込み中…
キャンセル
保存