浏览代码

Merge branch 'jitsi-meet-new'

master
damencho 9 年前
父节点
当前提交
c0dde18e6b
共有 92 个文件被更改,包括 37332 次插入14235 次删除
  1. 10
    0
      .editorconfig
  2. 1
    0
      .gitattributes
  3. 3
    0
      .jshintignore
  4. 2
    1
      .jshintrc
  5. 5
    2
      Makefile
  6. 85
    39
      app.js
  7. 820
    0
      conference.js
  8. 107
    0
      connection.js
  9. 5
    5
      css/videolayout_default.css
  10. 1
    1
      debian/control
  11. 5
    0
      debian/jitsi-meet-tokens.postinst
  12. 22
    2
      index.html
  13. 1
    1
      interface_config.js
  14. 31780
    0
      libs/lib-jitsi-meet.js
  15. 96
    67
      modules/API/API.js
  16. 0
    47
      modules/DTMF/DTMF.js
  17. 0
    211
      modules/RTC/DataChannels.js
  18. 0
    145
      modules/RTC/LocalStream.js
  19. 0
    57
      modules/RTC/MediaStream.js
  20. 0
    335
      modules/RTC/RTC.js
  21. 0
    576
      modules/RTC/RTCUtils.js
  22. 0
    1168
      modules/RTC/adapter.screenshare.js
  23. 5
    10
      modules/UI/Feedback.js
  24. 753
    624
      modules/UI/UI.js
  25. 158
    189
      modules/UI/audio_levels/AudioLevels.js
  26. 7
    10
      modules/UI/audio_levels/CanvasUtils.js
  27. 145
    0
      modules/UI/authentication/AuthHandler.js
  28. 0
    124
      modules/UI/authentication/Authentication.js
  29. 166
    170
      modules/UI/authentication/LoginDialog.js
  30. 189
    0
      modules/UI/authentication/RoomLocker.js
  31. 38
    44
      modules/UI/avatar/Avatar.js
  32. 138
    87
      modules/UI/etherpad/Etherpad.js
  33. 390
    285
      modules/UI/prezi/Prezi.js
  34. 276
    284
      modules/UI/prezi/PreziPlayer.js
  35. 105
    117
      modules/UI/side_pannels/SidePanelToggler.js
  36. 45
    59
      modules/UI/side_pannels/chat/Chat.js
  37. 11
    8
      modules/UI/side_pannels/chat/Commands.js
  38. 4
    11
      modules/UI/side_pannels/chat/Replacement.js
  39. 55
    75
      modules/UI/side_pannels/contactlist/ContactList.js
  40. 55
    66
      modules/UI/side_pannels/settings/SettingsMenu.js
  41. 92
    49
      modules/UI/toolbars/BottomToolbar.js
  42. 190
    539
      modules/UI/toolbars/Toolbar.js
  43. 43
    47
      modules/UI/toolbars/ToolbarToggler.js
  44. 0
    30
      modules/UI/util/NicknameHandler.js
  45. 38
    9
      modules/UI/util/UIUtil.js
  46. 10
    9
      modules/UI/videolayout/ConnectionIndicator.js
  47. 41
    0
      modules/UI/videolayout/LargeContainer.js
  48. 441
    577
      modules/UI/videolayout/LargeVideo.js
  49. 33
    62
      modules/UI/videolayout/LocalVideo.js
  50. 83
    78
      modules/UI/videolayout/RemoteVideo.js
  51. 74
    88
      modules/UI/videolayout/SmallVideo.js
  52. 543
    557
      modules/UI/videolayout/VideoLayout.js
  53. 2
    2
      modules/config/BoshAddressChoice.js
  54. 55
    63
      modules/connectionquality/connectionquality.js
  55. 0
    407
      modules/desktopsharing/ScreenObtainer.js
  56. 57
    60
      modules/desktopsharing/desktopsharing.js
  57. 4
    8
      modules/keyboardshortcut/keyboardshortcut.js
  58. 0
    128
      modules/members/MemberList.js
  59. 12
    23
      modules/settings/Settings.js
  60. 25
    26
      modules/statistics/AnalyticsAdapter.js
  61. 0
    273
      modules/statistics/CallStats.js
  62. 0
    128
      modules/statistics/LocalStatsCollector.js
  63. 12
    70
      modules/statistics/RTPStatsCollector.js
  64. 9
    100
      modules/statistics/statistics.js
  65. 6
    24
      modules/translation/translation.js
  66. 0
    32
      modules/util/ScriptUtil.js
  67. 2
    6
      modules/util/UsernameGenerator.js
  68. 14
    0
      modules/util/helpers.js
  69. 0
    127
      modules/xmpp/JingleSession.js
  70. 0
    1521
      modules/xmpp/JingleSessionPC.js
  71. 0
    643
      modules/xmpp/SDP.js
  72. 0
    168
      modules/xmpp/SDPDiffer.js
  73. 0
    362
      modules/xmpp/SDPUtil.js
  74. 0
    492
      modules/xmpp/TraceablePeerConnection.js
  75. 0
    442
      modules/xmpp/moderator.js
  76. 0
    178
      modules/xmpp/recording.js
  77. 0
    702
      modules/xmpp/strophe.emuc.js
  78. 0
    341
      modules/xmpp/strophe.jingle.js
  79. 0
    20
      modules/xmpp/strophe.logger.js
  80. 0
    61
      modules/xmpp/strophe.moderate.js
  81. 0
    121
      modules/xmpp/strophe.ping.js
  82. 0
    96
      modules/xmpp/strophe.rayo.js
  83. 0
    43
      modules/xmpp/strophe.util.js
  84. 0
    624
      modules/xmpp/xmpp.js
  85. 11
    2
      package.json
  86. 12
    0
      prosody-plugins/mod_bosh.lua.patch
  87. 0
    6
      service/RTC/MediaStreamTypes.js
  88. 0
    53
      service/RTC/Resolutions.js
  89. 39
    6
      service/UI/UIEvents.js
  90. 1
    5
      service/desktopsharing/DesktopSharingEventTypes.js
  91. 0
    5
      service/members/Events.js
  92. 0
    2
      service/xmpp/XMPPEvents.js

+ 10
- 0
.editorconfig 查看文件

@@ -0,0 +1,10 @@
1
+# http://editorconfig.org
2
+root = true
3
+
4
+[*]
5
+charset = utf-8
6
+end_of_line = lf
7
+indent_size = 4
8
+indent_style = space
9
+max_line_length = 80
10
+trim_trailing_whitespace = true

+ 1
- 0
.gitattributes 查看文件

@@ -1 +1,2 @@
1 1
 *.bundle.js -text -diff
2
+lib-jitsi-meet.js -text -diff

+ 3
- 0
.jshintignore 查看文件

@@ -2,7 +2,10 @@ node_modules
2 2
 libs
3 3
 debian
4 4
 analytics.js
5
+lib-jitsi-meet.js
5 6
 
6 7
 modules/xmpp/strophe.emuc.js
7 8
 modules/UI/prezi/Prezi.js
8 9
 modules/RTC/adapter.screenshare.js
10
+modules/statistics/*
11
+modules/UI/videolayout/*

+ 2
- 1
.jshintrc 查看文件

@@ -15,5 +15,6 @@
15 15
     "newcap": true, // true: Require capitalization of all constructor functions e.g. `new F()`
16 16
     "maxlen": 80, // {int} Max number of characters per line
17 17
     "latedef": false, //This option prohibits the use of a variable before it was defined
18
-    "laxbreak": true //Ignore line breaks around "=", "==", "&&", etc.
18
+    "laxbreak": true, //Ignore line breaks around "=", "==", "&&", etc.
19
+    "esnext": true //support ES2015
19 20
 }

+ 5
- 2
Makefile 查看文件

@@ -8,10 +8,13 @@ DEPLOY_DIR = libs
8 8
 BROWSERIFY_FLAGS = -d
9 9
 OUTPUT_DIR = .
10 10
 
11
-all: compile uglify deploy clean
11
+all: update-deps compile uglify deploy clean
12
+
13
+update-deps:
14
+	$(NPM) update
12 15
 
13 16
 compile:
14
-	$(NPM) update && $(BROWSERIFY) $(BROWSERIFY_FLAGS) -e app.js -s APP | $(EXORCIST) $(OUTPUT_DIR)/app.bundle.js.map > $(OUTPUT_DIR)/app.bundle.js
17
+	$(BROWSERIFY) $(BROWSERIFY_FLAGS) -e app.js -s APP | $(EXORCIST) $(OUTPUT_DIR)/app.bundle.js.map > $(OUTPUT_DIR)/app.bundle.js
15 18
 
16 19
 clean:
17 20
 	rm -f $(OUTPUT_DIR)/app.bundle.*

+ 85
- 39
app.js 查看文件

@@ -1,48 +1,99 @@
1
-/* jshint -W117 */
1
+/* global $, JitsiMeetJS, config */
2 2
 /* application specific logic */
3 3
 
4
-require("jquery");
5
-require("jquery-ui");
6
-require("strophe");
7
-require("strophe-disco");
8
-require("strophe-caps");
9
-require("tooltip");
10
-require("popover");
4
+import "babel-polyfill";
5
+import "jquery";
6
+import "jquery-ui";
7
+import "strophe";
8
+import "strophe-disco";
9
+import "strophe-caps";
10
+import "tooltip";
11
+import "popover";
12
+import "jQuery-Impromptu";
13
+import "autosize";
11 14
 window.toastr = require("toastr");
12
-require("jQuery-Impromptu");
13
-require("autosize");
14
-
15
-var APP =
16
-{
17
-    init: function () {
18
-        this.UI = require("./modules/UI/UI");
19
-        this.API = require("./modules/API/API");
15
+
16
+import URLProcessor from "./modules/config/URLProcessor";
17
+import RoomnameGenerator from './modules/util/RoomnameGenerator';
18
+
19
+import UI from "./modules/UI/UI";
20
+import statistics from "./modules/statistics/statistics";
21
+import settings from "./modules/settings/Settings";
22
+import conference from './conference';
23
+import API from './modules/API/API';
24
+
25
+import UIEvents from './service/UI/UIEvents';
26
+
27
+
28
+function buildRoomName () {
29
+    let path = window.location.pathname;
30
+    let roomName;
31
+
32
+    // determinde the room node from the url
33
+    // TODO: just the roomnode or the whole bare jid?
34
+    if (config.getroomnode && typeof config.getroomnode === 'function') {
35
+        // custom function might be responsible for doing the pushstate
36
+        roomName = config.getroomnode(path);
37
+    } else {
38
+        /* fall back to default strategy
39
+         * this is making assumptions about how the URL->room mapping happens.
40
+         * It currently assumes deployment at root, with a rewrite like the
41
+         * following one (for nginx):
42
+         location ~ ^/([a-zA-Z0-9]+)$ {
43
+         rewrite ^/(.*)$ / break;
44
+         }
45
+        */
46
+        if (path.length > 1) {
47
+            roomName = path.substr(1).toLowerCase();
48
+        } else {
49
+            let word = RoomnameGenerator.generateRoomWithoutSeparator();
50
+            roomName = word.toLowerCase();
51
+            window.history.pushState(
52
+                'VideoChat', `Room: ${word}`, window.location.pathname + word
53
+            );
54
+        }
55
+    }
56
+
57
+    return roomName;
58
+}
59
+
60
+const APP = {
61
+    UI,
62
+    statistics,
63
+    settings,
64
+    conference,
65
+    API,
66
+    init () {
20 67
         this.connectionquality =
21 68
             require("./modules/connectionquality/connectionquality");
22
-        this.statistics = require("./modules/statistics/statistics");
23
-        this.RTC = require("./modules/RTC/RTC");
24 69
         this.desktopsharing =
25 70
             require("./modules/desktopsharing/desktopsharing");
26
-        this.xmpp = require("./modules/xmpp/xmpp");
27 71
         this.keyboardshortcut =
28 72
             require("./modules/keyboardshortcut/keyboardshortcut");
29 73
         this.translation = require("./modules/translation/translation");
30
-        this.settings = require("./modules/settings/Settings");
31
-        //this.DTMF = require("./modules/DTMF/DTMF");
32
-        this.members = require("./modules/members/MemberList");
33 74
         this.configFetch = require("./modules/config/HttpConfigFetch");
34 75
     }
35 76
 };
36 77
 
37 78
 function init() {
79
+    var isUIReady = APP.UI.start();
80
+    if (isUIReady) {
81
+        APP.conference.init({roomName: buildRoomName()}).then(function () {
82
+            APP.UI.initConference();
83
+
84
+            APP.UI.addListener(UIEvents.LANG_CHANGED, function (language) {
85
+                APP.translation.setLanguage(language);
86
+                APP.settings.setLanguage(language);
87
+            });
38 88
 
39
-    APP.desktopsharing.init();
40
-    APP.RTC.start();
41
-    APP.xmpp.start();
42
-    APP.statistics.start();
43
-    APP.connectionquality.init();
44
-    APP.keyboardshortcut.init();
45
-    APP.members.start();
89
+            APP.desktopsharing.init(JitsiMeetJS.isDesktopSharingEnabled());
90
+            APP.statistics.start();
91
+            APP.connectionquality.init();
92
+            APP.keyboardshortcut.init();
93
+        }).catch(function (err) {
94
+            console.error(err);
95
+        });
96
+    }
46 97
 }
47 98
 
48 99
 /**
@@ -54,7 +105,7 @@ function init() {
54 105
  * will be displayed to the user.
55 106
  */
56 107
 function obtainConfigAndInit() {
57
-    var roomName = APP.UI.getRoomNode();
108
+    let roomName = APP.conference.roomName;
58 109
 
59 110
     if (config.configLocation) {
60 111
         APP.configFetch.obtainConfig(
@@ -84,23 +135,18 @@ function obtainConfigAndInit() {
84 135
 $(document).ready(function () {
85 136
     console.log("(TIME) document ready:\t", window.performance.now());
86 137
 
87
-    var URLProcessor = require("./modules/config/URLProcessor");
88 138
     URLProcessor.setConfigParametersFromUrl();
89 139
     APP.init();
90 140
 
91
-    APP.translation.init();
92
-
93
-    if(APP.API.isEnabled())
94
-        APP.API.init();
141
+    APP.translation.init(settings.getLanguage());
95 142
 
96
-    APP.UI.start(obtainConfigAndInit);
143
+    APP.API.init();
97 144
 
145
+    obtainConfigAndInit();
98 146
 });
99 147
 
100 148
 $(window).bind('beforeunload', function () {
101
-    if(APP.API.isEnabled())
102
-        APP.API.dispose();
149
+    APP.API.dispose();
103 150
 });
104 151
 
105 152
 module.exports = APP;
106
-

+ 820
- 0
conference.js 查看文件

@@ -0,0 +1,820 @@
1
+/* global $, APP, JitsiMeetJS, config, interfaceConfig */
2
+import {openConnection} from './connection';
3
+//FIXME:
4
+import createRoomLocker from './modules/UI/authentication/RoomLocker';
5
+//FIXME:
6
+import AuthHandler from './modules/UI/authentication/AuthHandler';
7
+
8
+import CQEvents from './service/connectionquality/CQEvents';
9
+import UIEvents from './service/UI/UIEvents';
10
+import DSEvents from './service/desktopsharing/DesktopSharingEventTypes';
11
+
12
+const ConnectionEvents = JitsiMeetJS.events.connection;
13
+const ConnectionErrors = JitsiMeetJS.errors.connection;
14
+
15
+const ConferenceEvents = JitsiMeetJS.events.conference;
16
+const ConferenceErrors = JitsiMeetJS.errors.conference;
17
+
18
+let room, connection, localTracks, localAudio, localVideo, roomLocker;
19
+
20
+/**
21
+ * Known custom conference commands.
22
+ */
23
+const Commands = {
24
+    CONNECTION_QUALITY: "stats",
25
+    EMAIL: "email",
26
+    VIDEO_TYPE: "videoType",
27
+    ETHERPAD: "etherpad",
28
+    PREZI: "prezi",
29
+    STOP_PREZI: "stop-prezi"
30
+};
31
+
32
+/**
33
+ * Open Connection. When authentication failed it shows auth dialog.
34
+ * @returns Promise<JitsiConnection>
35
+ */
36
+function connect() {
37
+    return openConnection({retry: true}).catch(function (err) {
38
+        if (err === ConnectionErrors.PASSWORD_REQUIRED) {
39
+            APP.UI.notifyTokenAuthFailed();
40
+        } else {
41
+            APP.UI.notifyConnectionFailed(err);
42
+        }
43
+        throw err;
44
+    });
45
+}
46
+
47
+/**
48
+ * Add local track to the conference and shares
49
+ * video type with other users if its video track.
50
+ * @param {JitsiLocalTrack} track local track
51
+ */
52
+function addTrack (track) {
53
+    room.addTrack(track);
54
+
55
+    if (track.isAudioTrack()) {
56
+        return;
57
+    }
58
+
59
+    room.removeCommand(Commands.VIDEO_TYPE);
60
+    room.sendCommand(Commands.VIDEO_TYPE, {
61
+        value: track.videoType,
62
+        attributes: {
63
+            xmlns: 'http://jitsi.org/jitmeet/video'
64
+        }
65
+    });
66
+}
67
+
68
+/**
69
+ * Share email with other users.
70
+ * @param {string} email new email
71
+ */
72
+function sendEmail (email) {
73
+    room.sendCommand(Commands.EMAIL, {
74
+        value: email,
75
+        attributes: {
76
+            id: room.myUserId()
77
+        }
78
+    });
79
+}
80
+
81
+/**
82
+ * Get user nickname by user id.
83
+ * @param {string} id user id
84
+ * @returns {string?} user nickname or undefined if user is unknown.
85
+ */
86
+function getDisplayName (id) {
87
+    if (APP.conference.isLocalId(id)) {
88
+        return APP.settings.getDisplayName();
89
+    }
90
+
91
+    let participant = room.getParticipantById(id);
92
+    if (participant && participant.getDisplayName()) {
93
+        return participant.getDisplayName();
94
+    }
95
+}
96
+
97
+class ConferenceConnector {
98
+    constructor(resolve, reject) {
99
+        this._resolve = resolve;
100
+        this._reject = reject;
101
+        this.reconnectTimeout = null;
102
+        room.on(ConferenceEvents.CONFERENCE_JOINED,
103
+            this._handleConferenceJoined.bind(this));
104
+        room.on(ConferenceEvents.CONFERENCE_FAILED,
105
+            this._onConferenceFailed.bind(this));
106
+        room.on(ConferenceEvents.CONFERENCE_ERROR,
107
+            this._onConferenceError.bind(this));
108
+    }
109
+    _handleConferenceFailed(err, msg) {
110
+        this._unsubscribe();
111
+        this._reject(err);
112
+    }
113
+    _onConferenceFailed(err, ...params) {
114
+        console.error('CONFERENCE FAILED:', err, params);
115
+        switch (err) {
116
+            // room is locked by the password
117
+        case ConferenceErrors.PASSWORD_REQUIRED:
118
+            APP.UI.markRoomLocked(true);
119
+            roomLocker.requirePassword().then(function () {
120
+                room.join(roomLocker.password);
121
+            });
122
+            break;
123
+
124
+        case ConferenceErrors.CONNECTION_ERROR:
125
+            {
126
+                let [msg] = params;
127
+                APP.UI.notifyConnectionFailed(msg);
128
+            }
129
+            break;
130
+
131
+        case ConferenceErrors.VIDEOBRIDGE_NOT_AVAILABLE:
132
+            APP.UI.notifyBridgeDown();
133
+            break;
134
+
135
+            // not enough rights to create conference
136
+        case ConferenceErrors.AUTHENTICATION_REQUIRED:
137
+            // schedule reconnect to check if someone else created the room
138
+            this.reconnectTimeout = setTimeout(function () {
139
+                room.join();
140
+            }, 5000);
141
+
142
+            // notify user that auth is required
143
+            AuthHandler.requireAuth(APP.conference.roomName);
144
+            break;
145
+
146
+        case ConferenceErrors.RESERVATION_ERROR:
147
+            {
148
+                let [code, msg] = params;
149
+                APP.UI.notifyReservationError(code, msg);
150
+            }
151
+            break;
152
+
153
+        case ConferenceErrors.GRACEFUL_SHUTDOWN:
154
+            APP.UI.notifyGracefulShudown();
155
+            break;
156
+
157
+        case ConferenceErrors.JINGLE_FATAL_ERROR:
158
+            APP.UI.notifyInternalError();
159
+            break;
160
+
161
+        case ConferenceErrors.CONFERENCE_DESTROYED:
162
+            {
163
+                let [reason] = params;
164
+                APP.UI.notifyConferenceDestroyed(reason);
165
+            }
166
+            break;
167
+
168
+        case ConferenceErrors.FOCUS_DISCONNECTED:
169
+            {
170
+                let [focus, retrySec] = params;
171
+                APP.UI.notifyFocusDisconnected(focus, retrySec);
172
+            }
173
+            break;
174
+
175
+        default:
176
+            this._handleConferenceFailed(err, ...params);
177
+        }
178
+    }
179
+    _onConferenceError(err, ...params) {
180
+        console.error('CONFERENCE Error:', err, params);
181
+        switch (err) {
182
+        case ConferenceErrors.CHAT_ERROR:
183
+            {
184
+                let [code, msg] = params;
185
+                APP.UI.showChatError(code, msg);
186
+            }
187
+            break;
188
+        default:
189
+            console.error("Unknown error.");
190
+        }
191
+    }
192
+    _unsubscribe() {
193
+        room.off(
194
+            ConferenceEvents.CONFERENCE_JOINED, this._handleConferenceJoined);
195
+        room.off(
196
+            ConferenceEvents.CONFERENCE_FAILED, this._onConferenceFailed);
197
+        if (this.reconnectTimeout !== null) {
198
+            clearTimeout(this.reconnectTimeout);
199
+        }
200
+        AuthHandler.closeAuth();
201
+    }
202
+    _handleConferenceJoined() {
203
+        this._unsubscribe();
204
+        this._resolve();
205
+    }
206
+    connect() {
207
+        room.join();
208
+    }
209
+}
210
+
211
+export default {
212
+    localId: undefined,
213
+    isModerator: false,
214
+    audioMuted: false,
215
+    videoMuted: false,
216
+    /**
217
+     * Open new connection and join to the conference.
218
+     * @param {object} options
219
+     * @param {string} roomName name of the conference
220
+     * @returns {Promise}
221
+     */
222
+    init(options) {
223
+        this.roomName = options.roomName;
224
+        JitsiMeetJS.setLogLevel(JitsiMeetJS.logLevels.TRACE);
225
+
226
+        return JitsiMeetJS.init(config).then(() => {
227
+            return Promise.all([
228
+                this.createLocalTracks('audio', 'video').catch(
229
+                    () => {return [];}),
230
+                connect()
231
+            ]);
232
+        }).then(([tracks, con]) => {
233
+            console.log('initialized with %s local tracks', tracks.length);
234
+            localTracks = tracks;
235
+            connection = con;
236
+            this._createRoom();
237
+            // XXX The API will take care of disconnecting from the XMPP server
238
+            // (and, thus, leaving the room) on unload.
239
+            return new Promise((resolve, reject) => {
240
+                (new ConferenceConnector(resolve, reject)).connect();
241
+            });
242
+        });
243
+    },
244
+    /**
245
+     * Create local tracks of specified types.
246
+     * If we cannot obtain required tracks it will return empty array.
247
+     * @param {string[]} devices required track types ('audio', 'video' etc.)
248
+     * @returns {Promise<JitsiLocalTrack[]>}
249
+     */
250
+    createLocalTracks (...devices) {
251
+        return JitsiMeetJS.createLocalTracks({
252
+            // copy array to avoid mutations inside library
253
+            devices: devices.slice(0),
254
+            resolution: config.resolution,
255
+            // adds any ff fake device settings if any
256
+            firefox_fake_device: config.firefox_fake_device
257
+        }).catch(function (err) {
258
+            console.error('failed to create local tracks', ...devices, err);
259
+            APP.statistics.onGetUserMediaFailed(err);
260
+            return Promise.reject(err);
261
+        });
262
+    },
263
+    /**
264
+     * Check if id is id of the local user.
265
+     * @param {string} id id to check
266
+     * @returns {boolean}
267
+     */
268
+    isLocalId (id) {
269
+        return this.localId === id;
270
+    },
271
+    /**
272
+     * Simulates toolbar button click for audio mute. Used by shortcuts and API.
273
+     * @param mute true for mute and false for unmute.
274
+     */
275
+    muteAudio (mute) {
276
+        //FIXME: Maybe we should create method for that in the UI instead of
277
+        //accessing directly eventEmitter????
278
+        APP.UI.eventEmitter.emit(UIEvents.AUDIO_MUTED, mute);
279
+    },
280
+    /**
281
+     * Simulates toolbar button click for audio mute. Used by shortcuts and API.
282
+     */
283
+    toggleAudioMuted () {
284
+        this.muteAudio(!this.audioMuted);
285
+    },
286
+    /**
287
+     * Simulates toolbar button click for video mute. Used by shortcuts and API.
288
+     * @param mute true for mute and false for unmute.
289
+     */
290
+    muteVideo (mute) {
291
+        //FIXME: Maybe we should create method for that in the UI instead of
292
+        //accessing directly eventEmitter????
293
+        APP.UI.eventEmitter.emit(UIEvents.VIDEO_MUTED, mute);
294
+    },
295
+    /**
296
+     * Simulates toolbar button click for video mute. Used by shortcuts and API.
297
+     */
298
+    toggleVideoMuted () {
299
+        this.muteVideo(!this.videoMuted);
300
+    },
301
+    /**
302
+     * Retrieve list of conference participants (without local user).
303
+     * @returns {JitsiParticipant[]}
304
+     */
305
+    listMembers () {
306
+        return room.getParticipants();
307
+    },
308
+    /**
309
+     * Retrieve list of ids of conference participants (without local user).
310
+     * @returns {string[]}
311
+     */
312
+    listMembersIds () {
313
+        return room.getParticipants().map(p => p.getId());
314
+    },
315
+    /**
316
+     * Check if SIP is supported.
317
+     * @returns {boolean}
318
+     */
319
+    sipGatewayEnabled () {
320
+        return room.isSIPCallingSupported();
321
+    },
322
+    get membersCount () {
323
+        return room.getParticipants().length + 1;
324
+    },
325
+    get startAudioMuted () {
326
+        return room && room.getStartMutedPolicy().audio;
327
+    },
328
+    get startVideoMuted () {
329
+        return room && room.getStartMutedPolicy().video;
330
+    },
331
+    /**
332
+     * Returns true if the callstats integration is enabled, otherwise returns
333
+     * false.
334
+     *
335
+     * @returns true if the callstats integration is enabled, otherwise returns
336
+     * false.
337
+     */
338
+    isCallstatsEnabled () {
339
+        return room.isCallstatsEnabled();
340
+    },
341
+    /**
342
+     * Sends the given feedback through CallStats if enabled.
343
+     *
344
+     * @param overallFeedback an integer between 1 and 5 indicating the
345
+     * user feedback
346
+     * @param detailedFeedback detailed feedback from the user. Not yet used
347
+     */
348
+    sendFeedback (overallFeedback, detailedFeedback) {
349
+        return room.sendFeedback (overallFeedback, detailedFeedback);
350
+    },
351
+    // used by torture currently
352
+    isJoined () {
353
+        return this._room
354
+            && this._room.isJoined();
355
+    },
356
+    getConnectionState () {
357
+        return this._room
358
+            && this._room.getConnectionState();
359
+    },
360
+    getMyUserId () {
361
+        return this._room
362
+            && this._room.myUserId();
363
+    },
364
+    /**
365
+     * Will be filled with values only when config.debug is enabled.
366
+     * Its used by torture to check audio levels.
367
+     */
368
+    audioLevelsMap: {},
369
+    getPeerSSRCAudioLevel (id) {
370
+        return this.audioLevelsMap[id];
371
+    },
372
+    /**
373
+     * Will check for number of remote particiapnts that have at least one
374
+     * remote track.
375
+     * @return {boolean} whether we have enough participants with remote streams
376
+     */
377
+    checkEnoughParticipants (number) {
378
+        var participants = this._room.getParticipants();
379
+
380
+        var foundParticipants = 0;
381
+        for (var i = 0; i < participants.length; i += 1) {
382
+            if (participants[i].getTracks().length > 0) {
383
+                foundParticipants++;
384
+            }
385
+        }
386
+        return foundParticipants >= number;
387
+    },
388
+    // end used by torture
389
+
390
+    getLogs () {
391
+        return room.getLogs();
392
+    },
393
+    _createRoom () {
394
+        room = connection.initJitsiConference(APP.conference.roomName,
395
+            this._getConferenceOptions());
396
+        this.localId = room.myUserId();
397
+        localTracks.forEach((track) => {
398
+            if(track.isAudioTrack()) {
399
+                localAudio = track;
400
+            }
401
+            else if (track.isVideoTrack()) {
402
+                localVideo = track;
403
+            }
404
+            addTrack(track);
405
+            APP.UI.addLocalStream(track);
406
+        });
407
+        roomLocker = createRoomLocker(room);
408
+        this._room = room; // FIXME do not use this
409
+        this.localId = room.myUserId();
410
+
411
+        let email = APP.settings.getEmail();
412
+        email && sendEmail(email);
413
+
414
+        let nick = APP.settings.getDisplayName();
415
+        (config.useNicks && !nick) && (() => {
416
+            nick = APP.UI.askForNickname();
417
+            APP.settings.setDisplayName(nick);
418
+        })();
419
+        nick && room.setDisplayName(nick);
420
+
421
+        this._setupListeners();
422
+    },
423
+    _getConferenceOptions() {
424
+        let options = config;
425
+        if(config.enableRecording) {
426
+            options.recordingType = (config.hosts &&
427
+                (typeof config.hosts.jirecon != "undefined"))?
428
+                "jirecon" : "colibri";
429
+        }
430
+        return options;
431
+    },
432
+    /**
433
+     * Setup interaction between conference and UI.
434
+     */
435
+    _setupListeners () {
436
+        // add local streams when joined to the conference
437
+        room.on(ConferenceEvents.CONFERENCE_JOINED, () => {
438
+            APP.UI.updateAuthInfo(room.isAuthEnabled(), room.getAuthLogin());
439
+            APP.UI.mucJoined();
440
+        });
441
+
442
+
443
+        room.on(ConferenceEvents.USER_JOINED, (id, user) => {
444
+            console.log('USER %s connnected', id, user);
445
+            APP.API.notifyUserJoined(id);
446
+            // FIXME email???
447
+            APP.UI.addUser(id, user.getDisplayName());
448
+
449
+            // chek the roles for the new user and reflect them
450
+            APP.UI.updateUserRole(user);
451
+        });
452
+        room.on(ConferenceEvents.USER_LEFT, (id, user) => {
453
+            console.log('USER %s LEFT', id, user);
454
+            APP.API.notifyUserLeft(id);
455
+            APP.UI.removeUser(id, user.getDisplayName());
456
+            APP.UI.stopPrezi(id);
457
+        });
458
+
459
+
460
+        room.on(ConferenceEvents.USER_ROLE_CHANGED, (id, role) => {
461
+            if (this.isLocalId(id)) {
462
+                console.info(`My role changed, new role: ${role}`);
463
+                this.isModerator = room.isModerator();
464
+                APP.UI.updateLocalRole(room.isModerator());
465
+            } else {
466
+                let user = room.getParticipantById(id);
467
+                if (user) {
468
+                    APP.UI.updateUserRole(user);
469
+                }
470
+            }
471
+        });
472
+
473
+        room.on(ConferenceEvents.TRACK_ADDED, (track) => {
474
+            if(!track || track.isLocal())
475
+                return;
476
+            APP.UI.addRemoteStream(track);
477
+        });
478
+
479
+        room.on(ConferenceEvents.TRACK_REMOVED, (track) => {
480
+            // FIXME handle
481
+        });
482
+
483
+        room.on(ConferenceEvents.TRACK_MUTE_CHANGED, (track) => {
484
+            if(!track)
485
+                return;
486
+            const handler = (track.getType() === "audio")?
487
+                APP.UI.setAudioMuted : APP.UI.setVideoMuted;
488
+            let id;
489
+            const mute = track.isMuted();
490
+            if(track.isLocal()){
491
+                id = this.localId;
492
+                if(track.getType() === "audio") {
493
+                    this.audioMuted = mute;
494
+                } else {
495
+                    this.videoMuted = mute;
496
+                }
497
+            } else {
498
+                id = track.getParticipantId();
499
+            }
500
+            handler(id , mute);
501
+        });
502
+        room.on(ConferenceEvents.TRACK_AUDIO_LEVEL_CHANGED, (id, lvl) => {
503
+            if(this.isLocalId(id) && localAudio.isMuted()) {
504
+                lvl = 0;
505
+            }
506
+
507
+            if(config.debug)
508
+                this.audioLevelsMap[id] = lvl;
509
+
510
+            APP.UI.setAudioLevel(id, lvl);
511
+        });
512
+
513
+        room.on(ConferenceEvents.IN_LAST_N_CHANGED, (inLastN) => {
514
+            //FIXME
515
+            if (config.muteLocalVideoIfNotInLastN) {
516
+                // TODO mute or unmute if required
517
+                // mark video on UI
518
+                // APP.UI.markVideoMuted(true/false);
519
+            }
520
+        });
521
+        room.on(
522
+            ConferenceEvents.LAST_N_ENDPOINTS_CHANGED, (ids, enteringIds) => {
523
+            APP.UI.handleLastNEndpoints(ids, enteringIds);
524
+        });
525
+        room.on(ConferenceEvents.DOMINANT_SPEAKER_CHANGED, (id) => {
526
+            APP.UI.markDominantSpeaker(id);
527
+        });
528
+
529
+        if (!interfaceConfig.filmStripOnly) {
530
+            room.on(ConferenceEvents.CONNECTION_INTERRUPTED, () => {
531
+                APP.UI.markVideoInterrupted(true);
532
+            });
533
+            room.on(ConferenceEvents.CONNECTION_RESTORED, () => {
534
+                APP.UI.markVideoInterrupted(false);
535
+            });
536
+            room.on(ConferenceEvents.MESSAGE_RECEIVED, (id, text, ts) => {
537
+                let nick = getDisplayName(id);
538
+                APP.API.notifyReceivedChatMessage(id, nick, text, ts);
539
+                APP.UI.addMessage(id, nick, text, ts);
540
+            });
541
+        }
542
+
543
+        room.on(ConferenceEvents.DISPLAY_NAME_CHANGED, (id, displayName) => {
544
+            APP.API.notifyDisplayNameChanged(id, displayName);
545
+            APP.UI.changeDisplayName(id, displayName);
546
+        });
547
+
548
+        room.on(ConferenceEvents.RECORDING_STATE_CHANGED, (status, error) => {
549
+            if(status == "error") {
550
+                console.error(error);
551
+                return;
552
+            }
553
+            APP.UI.updateRecordingState(status);
554
+        });
555
+
556
+        room.on(ConferenceEvents.USER_STATUS_CHANGED, function (id, status) {
557
+            APP.UI.updateUserStatus(id, status);
558
+        });
559
+
560
+        room.on(ConferenceEvents.KICKED, () => {
561
+            APP.UI.notifyKicked();
562
+            // FIXME close
563
+        });
564
+
565
+        room.on(ConferenceEvents.DTMF_SUPPORT_CHANGED, (isDTMFSupported) => {
566
+            APP.UI.updateDTMFSupport(isDTMFSupported);
567
+        });
568
+
569
+        room.on(ConferenceEvents.FIREFOX_EXTENSION_NEEDED, function (url) {
570
+            APP.UI.notifyFirefoxExtensionRequired(url);
571
+        });
572
+
573
+        APP.UI.addListener(UIEvents.ROOM_LOCK_CLICKED, () => {
574
+            if (room.isModerator()) {
575
+                let promise = roomLocker.isLocked
576
+                    ? roomLocker.askToUnlock()
577
+                    : roomLocker.askToLock();
578
+                promise.then(() => {
579
+                    APP.UI.markRoomLocked(roomLocker.isLocked);
580
+                });
581
+            } else {
582
+                roomLocker.notifyModeratorRequired();
583
+            }
584
+        });
585
+
586
+        APP.UI.addListener(UIEvents.AUDIO_MUTED, (muted) => {
587
+            (muted)? localAudio.mute() : localAudio.unmute();
588
+        });
589
+        APP.UI.addListener(UIEvents.VIDEO_MUTED, (muted) => {
590
+            (muted)? localVideo.mute() : localVideo.unmute();
591
+        });
592
+
593
+        if (!interfaceConfig.filmStripOnly) {
594
+            APP.UI.addListener(UIEvents.MESSAGE_CREATED, (message) => {
595
+                APP.API.notifySendingChatMessage(message);
596
+                room.sendTextMessage(message);
597
+            });
598
+        }
599
+
600
+        APP.connectionquality.addListener(
601
+            CQEvents.LOCALSTATS_UPDATED,
602
+            (percent, stats) => {
603
+                APP.UI.updateLocalStats(percent, stats);
604
+
605
+                // send local stats to other users
606
+                room.sendCommandOnce(Commands.CONNECTION_QUALITY, {
607
+                    children: APP.connectionquality.convertToMUCStats(stats),
608
+                    attributes: {
609
+                        xmlns: 'http://jitsi.org/jitmeet/stats'
610
+                    }
611
+                });
612
+            }
613
+        );
614
+
615
+        APP.connectionquality.addListener(CQEvents.STOP, () => {
616
+            APP.UI.hideStats();
617
+            room.removeCommand(Commands.CONNECTION_QUALITY);
618
+        });
619
+
620
+        // listen to remote stats
621
+        room.addCommandListener(Commands.CONNECTION_QUALITY,(values, from) => {
622
+            APP.connectionquality.updateRemoteStats(from, values);
623
+        });
624
+
625
+        APP.connectionquality.addListener(CQEvents.REMOTESTATS_UPDATED,
626
+            (id, percent, stats) => {
627
+                APP.UI.updateRemoteStats(id, percent, stats);
628
+            });
629
+
630
+        room.addCommandListener(Commands.ETHERPAD, ({value}) => {
631
+            APP.UI.initEtherpad(value);
632
+        });
633
+
634
+        room.addCommandListener(Commands.PREZI, ({value, attributes}) => {
635
+            APP.UI.showPrezi(attributes.id, value, attributes.slide);
636
+        });
637
+
638
+        room.addCommandListener(Commands.STOP_PREZI, ({attributes}) => {
639
+            APP.UI.stopPrezi(attributes.id);
640
+        });
641
+
642
+        APP.UI.addListener(UIEvents.SHARE_PREZI, (url, slide) => {
643
+            console.log('Sharing Prezi %s slide %s', url, slide);
644
+            room.removeCommand(Commands.PREZI);
645
+            room.sendCommand(Commands.PREZI, {
646
+                value: url,
647
+                attributes: {
648
+                    id: room.myUserId(),
649
+                    slide
650
+                }
651
+            });
652
+        });
653
+
654
+        APP.UI.addListener(UIEvents.STOP_SHARING_PREZI, () => {
655
+            room.removeCommand(Commands.PREZI);
656
+            room.sendCommandOnce(Commands.STOP_PREZI, {
657
+                attributes: {
658
+                    id: room.myUserId()
659
+                }
660
+            });
661
+        });
662
+
663
+        room.addCommandListener(Commands.VIDEO_TYPE, ({value}, from) => {
664
+            APP.UI.onPeerVideoTypeChanged(from, value);
665
+        });
666
+
667
+        APP.UI.addListener(UIEvents.EMAIL_CHANGED, (email) => {
668
+            APP.settings.setEmail(email);
669
+            APP.UI.setUserAvatar(room.myUserId(), email);
670
+            sendEmail(email);
671
+        });
672
+        room.addCommandListener(Commands.EMAIL, (data) => {
673
+            APP.UI.setUserAvatar(data.attributes.id, data.value);
674
+        });
675
+
676
+        APP.UI.addListener(UIEvents.NICKNAME_CHANGED, (nickname) => {
677
+            APP.settings.setDisplayName(nickname);
678
+            room.setDisplayName(nickname);
679
+            APP.UI.changeDisplayName(APP.conference.localId, nickname);
680
+        });
681
+
682
+        APP.UI.addListener(UIEvents.START_MUTED_CHANGED,
683
+            (startAudioMuted, startVideoMuted) => {
684
+                room.setStartMutedPolicy({audio: startAudioMuted,
685
+                    video: startVideoMuted});
686
+            }
687
+        );
688
+        room.on(
689
+            ConferenceEvents.START_MUTED_POLICY_CHANGED,
690
+            (policy) => {
691
+                APP.UI.onStartMutedChanged();
692
+            }
693
+        );
694
+        room.on(ConferenceEvents.STARTED_MUTED, () => {
695
+            (room.isStartAudioMuted() || room.isStartVideoMuted())
696
+                && APP.UI.notifyInitiallyMuted();
697
+        });
698
+
699
+        APP.UI.addListener(UIEvents.USER_INVITED, (roomUrl) => {
700
+            APP.UI.inviteParticipants(
701
+                roomUrl,
702
+                APP.conference.roomName,
703
+                roomLocker.password,
704
+                APP.settings.getDisplayName()
705
+            );
706
+        });
707
+
708
+        room.on(
709
+            ConferenceEvents.AVAILABLE_DEVICES_CHANGED, function (id, devices) {
710
+                APP.UI.updateDevicesAvailability(id, devices);
711
+            }
712
+        );
713
+
714
+        // call hangup
715
+        APP.UI.addListener(UIEvents.HANGUP, () => {
716
+            APP.UI.requestFeedback().then(() => {
717
+                connection.disconnect();
718
+                config.enableWelcomePage && setTimeout(() => {
719
+                        window.localStorage.welcomePageDisabled = false;
720
+                        window.location.pathname = "/";
721
+                    }, 3000);
722
+            }, (err) => {console.error(err);});
723
+        });
724
+
725
+        // logout
726
+        APP.UI.addListener(UIEvents.LOGOUT, () => {
727
+            // FIXME handle logout
728
+            // APP.xmpp.logout(function (url) {
729
+            //     if (url) {
730
+            //         window.location.href = url;
731
+            //     } else {
732
+            //         hangup();
733
+            //     }
734
+            // });
735
+        });
736
+
737
+        APP.UI.addListener(UIEvents.SIP_DIAL, (sipNumber) => {
738
+            room.dial(sipNumber);
739
+        });
740
+
741
+
742
+        // Starts or stops the recording for the conference.
743
+        APP.UI.addListener(UIEvents.RECORDING_TOGGLE, (predefinedToken) => {
744
+            if (predefinedToken) {
745
+                room.toggleRecording({token: predefinedToken});
746
+                return;
747
+            }
748
+            APP.UI.requestRecordingToken().then((token) => {
749
+                room.toggleRecording({token: token});
750
+            });
751
+
752
+        });
753
+
754
+        APP.UI.addListener(UIEvents.SUBJECT_CHANGED, (topic) => {
755
+            room.setSubject(topic);
756
+        });
757
+        room.on(ConferenceEvents.SUBJECT_CHANGED, function (subject) {
758
+            APP.UI.setSubject(subject);
759
+        });
760
+
761
+        APP.UI.addListener(UIEvents.USER_KICKED, (id) => {
762
+            room.kickParticipant(id);
763
+        });
764
+
765
+        APP.UI.addListener(UIEvents.REMOTE_AUDIO_MUTED, (id) => {
766
+            room.muteParticipant(id);
767
+        });
768
+
769
+        APP.UI.addListener(UIEvents.AUTH_CLICKED, () => {
770
+            AuthHandler.authenticate(room);
771
+        });
772
+
773
+        APP.UI.addListener(UIEvents.SELECTED_ENDPOINT, (id) => {
774
+            room.selectParticipant(id);
775
+        });
776
+        APP.UI.addListener(UIEvents.PINNED_ENDPOINT, (id) => {
777
+            room.pinParticipant(id);
778
+        });
779
+
780
+        APP.UI.addListener(UIEvents.TOGGLE_SCREENSHARING, () => {
781
+            APP.desktopsharing.toggleScreenSharing();
782
+        });
783
+
784
+        APP.desktopsharing.addListener(DSEvents.SWITCHING_DONE,
785
+        (isSharingScreen) => {
786
+            APP.UI.updateDesktopSharingButtons(isSharingScreen);
787
+        });
788
+
789
+        APP.desktopsharing.addListener(DSEvents.FIREFOX_EXTENSION_NEEDED,
790
+            (url) => {
791
+                APP.UI.showExtensionRequiredDialog(url);
792
+            });
793
+
794
+        APP.desktopsharing.addListener(DSEvents.NEW_STREAM_CREATED,
795
+            (track, callback) => {
796
+                const localCallback = (newTrack) => {
797
+                    if(!newTrack || !newTrack.isLocal() ||
798
+                        newTrack !== localVideo)
799
+                        return;
800
+                    if(localVideo.isMuted() &&
801
+                       localVideo.videoType !== track.videoType) {
802
+                        localVideo.mute();
803
+                    }
804
+                    callback();
805
+                    if(room)
806
+                        room.off(ConferenceEvents.TRACK_ADDED, localCallback);
807
+                };
808
+                if(room) {
809
+                    room.on(ConferenceEvents.TRACK_ADDED, localCallback);
810
+                }
811
+                localVideo.stop();
812
+                localVideo = track;
813
+                addTrack(track);
814
+                if(!room)
815
+                    localCallback();
816
+                APP.UI.addLocalStream(track);
817
+            }
818
+        );
819
+    }
820
+};

+ 107
- 0
connection.js 查看文件

@@ -0,0 +1,107 @@
1
+/* global APP, JitsiMeetJS, config */
2
+//FIXME:
3
+import LoginDialog from './modules/UI/authentication/LoginDialog';
4
+
5
+const ConnectionEvents = JitsiMeetJS.events.connection;
6
+const ConnectionErrors = JitsiMeetJS.errors.connection;
7
+
8
+/**
9
+ * Try to open connection using provided credentials.
10
+ * @param {string} [id]
11
+ * @param {string} [password]
12
+ * @returns {Promise<JitsiConnection>} connection if
13
+ * everything is ok, else error.
14
+ */
15
+function connect(id, password) {
16
+    let connection = new JitsiMeetJS.JitsiConnection(null, null, config);
17
+
18
+    return new Promise(function (resolve, reject) {
19
+        connection.addEventListener(
20
+            ConnectionEvents.CONNECTION_ESTABLISHED, handleConnectionEstablished
21
+        );
22
+        connection.addEventListener(
23
+            ConnectionEvents.CONNECTION_FAILED, handleConnectionFailed
24
+        );
25
+
26
+        function unsubscribe() {
27
+            connection.removeEventListener(
28
+                ConnectionEvents.CONNECTION_ESTABLISHED,
29
+                handleConnectionEstablished
30
+            );
31
+            connection.removeEventListener(
32
+                ConnectionEvents.CONNECTION_FAILED,
33
+                handleConnectionFailed
34
+            );
35
+        }
36
+
37
+        function handleConnectionEstablished() {
38
+            unsubscribe();
39
+            resolve(connection);
40
+        }
41
+
42
+        function handleConnectionFailed(err) {
43
+            unsubscribe();
44
+            console.error("CONNECTION FAILED:", err);
45
+            reject(err);
46
+        }
47
+
48
+        connection.connect({id, password});
49
+    });
50
+}
51
+
52
+/**
53
+ * Show Authentication Dialog and try to connect with new credentials.
54
+ * If failed to connect because of PASSWORD_REQUIRED error
55
+ * then ask for password again.
56
+ * @returns {Promise<JitsiConnection>}
57
+ */
58
+function requestAuth() {
59
+    return new Promise(function (resolve, reject) {
60
+        let authDialog = LoginDialog.showAuthDialog(
61
+            function (id, password) {
62
+                connect(id, password).then(function (connection) {
63
+                    authDialog.close();
64
+                    resolve(connection);
65
+                }, function (err) {
66
+                    if (err === ConnectionErrors.PASSWORD_REQUIRED) {
67
+                        authDialog.displayError(err);
68
+                    } else {
69
+                        authDialog.close();
70
+                        reject(err);
71
+                    }
72
+                });
73
+            }
74
+        );
75
+    });
76
+}
77
+
78
+/**
79
+ * Open JitsiConnection using provided credentials.
80
+ * If retry option is true it will show auth dialog on PASSWORD_REQUIRED error.
81
+ *
82
+ * @param {object} options
83
+ * @param {string} [options.id]
84
+ * @param {string} [options.password]
85
+ * @param {boolean} [retry] if we should show auth dialog
86
+ * on PASSWORD_REQUIRED error.
87
+ *
88
+ * @returns {Promise<JitsiConnection>}
89
+ */
90
+export function openConnection({id, password, retry}) {
91
+    return connect(id, password).catch(function (err) {
92
+        if (!retry) {
93
+            throw err;
94
+        }
95
+
96
+        if (err === ConnectionErrors.PASSWORD_REQUIRED) {
97
+            // do not retry if token is not valid
98
+            if (config.token) {
99
+                throw err;
100
+            } else {
101
+                return requestAuth();
102
+            }
103
+        } else {
104
+            throw err;
105
+        }
106
+    });
107
+}

+ 5
- 5
css/videolayout_default.css 查看文件

@@ -34,7 +34,7 @@
34 34
 }
35 35
 
36 36
 #remoteVideos .videocontainer {
37
-    display: inline-block;
37
+    display: none;
38 38
     background-color: black;
39 39
     background-size: contain;
40 40
     border-radius:8px;
@@ -378,7 +378,7 @@
378 378
     padding-right:2px;
379 379
     height:38px;
380 380
     width:auto;
381
-    background-color: rgba(0,0,0,0.8); 
381
+    background-color: rgba(0,0,0,0.8);
382 382
     border: 1px solid rgba(256, 256, 256, 0.2);
383 383
     border-radius: 6px;
384 384
     pointer-events: auto;
@@ -407,7 +407,7 @@
407 407
     pointer-events: none;
408 408
 }
409 409
 
410
-#activeSpeaker {
410
+#dominantSpeaker {
411 411
     visibility: hidden;
412 412
     width: 150px;
413 413
     height: 150px;
@@ -416,7 +416,7 @@
416 416
     position: relative;
417 417
 }
418 418
 
419
-#activeSpeakerAudioLevel {
419
+#dominantSpeakerAudioLevel {
420 420
     position: absolute;
421 421
     top: 0px;
422 422
     left: 0px;
@@ -428,7 +428,7 @@
428 428
     display:none !important;
429 429
 }
430 430
 
431
-#activeSpeakerAvatar {
431
+#dominantSpeakerAvatar {
432 432
     width: 100px;
433 433
     height: 100px;
434 434
     top: 25px;

+ 1
- 1
debian/control 查看文件

@@ -35,6 +35,6 @@ Description: Prosody configuration for Jitsi Meet
35 35
 
36 36
 Package: jitsi-meet-tokens
37 37
 Architecture: all
38
-Depends: ${misc:Depends}, prosody-trunk (>= 1nightly603), libssl-dev, luarocks, jitsi-meet-prosody
38
+Depends: ${misc:Depends}, prosody-trunk (>= 1nightly607), libssl-dev, luarocks, jitsi-meet-prosody
39 39
 Description: Prosody token authentication plugin for Jitsi Meet
40 40
 

+ 5
- 0
debian/jitsi-meet-tokens.postinst 查看文件

@@ -74,6 +74,11 @@ case "$1" in
74 74
                 if [ -x "/etc/init.d/prosody" ]; then
75 75
                     invoke-rc.d prosody restart
76 76
                 fi
77
+
78
+                echo "This package requires BOSH Prosody module to be patched !"
79
+                echo "Use the following command, after this package has been installed and"
80
+                echo "after every prosody-trunk upgrade:"
81
+                echo "sudo patch -N /usr/lib/prosody/modules/mod_bosh.lua /usr/share/jitsi-meet/prosody-plugins/mod_bosh.lua.patch"
77 82
             else
78 83
                 echo "Failed apply auto-config to $PROSODY_HOST_CONFIG which most likely comes from not supported version of jitsi-meet"
79 84
             fi

+ 22
- 2
index.html 查看文件

@@ -13,6 +13,7 @@
13 13
     <script>console.log("(TIME) index.html loaded:\t", window.performance.now());</script>
14 14
     <script src="config.js?v=15"></script><!-- adapt to your needs, i.e. set hosts and bosh path -->
15 15
     <script src="interface_config.js?v=6"></script>
16
+    <script src="libs/lib-jitsi-meet.js?v=139"></script>
16 17
     <script src="libs/app.bundle.min.js?v=139"></script>
17 18
     <!--
18 19
         Link used for inline installation of chrome desktop streaming extension,
@@ -139,13 +140,32 @@
139 140
         </div>
140 141
         <div id="reloadPresentation"><a id="reloadPresentationLink"><i title="Reload Prezi" class="fa fa-repeat fa-lg"></i></a></div>
141 142
         <div id="videospace">
143
+
144
+            <div id="largeVideoContainer" class="videocontainer">
145
+                <div id="presentation"></div>
146
+                <div id="etherpad"></div>
147
+                <a target="_new"><div class="watermark leftwatermark"></div></a>
148
+                <a target="_new"><div class="watermark rightwatermark"></div></a>
149
+                <a class="poweredby" href="http://jitsi.org" target="_new">
150
+                    <span data-i18n="poweredby"></span> jitsi.org
151
+                </a>
152
+                <div id="dominantSpeaker">
153
+                    <img id="dominantSpeakerAvatar" src=""/>
154
+                    <canvas id="dominantSpeakerAudioLevel"></canvas>
155
+                </div>
156
+                <div id="largeVideoWrapper">
157
+                    <video id="largeVideo" muted="true" autoplay></video>
158
+                </div>
159
+                <span id="videoConnectionMessage"></span>
160
+            </div>
161
+
142 162
             <div id="remoteVideos">
143 163
                 <span id="localVideoContainer" class="videocontainer">
144 164
                     <span id="localNick" class="nick"></span>
145 165
                     <span id="localVideoWrapper">
146
-                        <!--<video id="localVideo" autoplay oncontextmenu="return false;" muted></video> - is now per stream generated -->
166
+                        <!--<video id="localVideo" autoplay muted></video> - is now per stream generated -->
147 167
                     </span>
148
-                    <audio id="localAudio" autoplay oncontextmenu="return false;" muted></audio>
168
+                    <audio id="localAudio" autoplay muted></audio>
149 169
                     <span class="focusindicator"></span>
150 170
                 </span>
151 171
                 <audio id="userJoined" src="sounds/joined.wav" preload="auto"></audio>

+ 1
- 1
interface_config.js 查看文件

@@ -14,7 +14,7 @@ var interfaceConfig = {
14 14
     GENERATE_ROOMNAMES_ON_WELCOME_PAGE: true,
15 15
     APP_NAME: "Jitsi Meet",
16 16
     INVITATION_POWERED_BY: true,
17
-    ACTIVE_SPEAKER_AVATAR_SIZE: 100,
17
+    DOMINANT_SPEAKER_AVATAR_SIZE: 100,
18 18
     TOOLBAR_BUTTONS: ['authentication', 'microphone', 'camera', 'desktop',
19 19
         'recording', 'security', 'invite', 'chat', 'prezi', 'etherpad',
20 20
         'fullscreen', 'sip', 'dialpad', 'settings', 'hangup', 'filmstrip',

+ 31780
- 0
libs/lib-jitsi-meet.js
文件差异内容过多而无法显示
查看文件


+ 96
- 67
modules/API/API.js 查看文件

@@ -5,8 +5,6 @@
5 5
  * applications that embed Jitsi Meet
6 6
  */
7 7
 
8
-var XMPPEvents = require("../../service/xmpp/XMPPEvents");
9
-
10 8
 /**
11 9
  * List of the available commands.
12 10
  * @type {{
@@ -23,8 +21,8 @@ var commands = {};
23 21
 function initCommands() {
24 22
     commands = {
25 23
         displayName: APP.UI.inputDisplayNameHandler,
26
-        toggleAudio: APP.UI.toggleAudio,
27
-        toggleVideo: APP.UI.toggleVideo,
24
+        toggleAudio: APP.conference.toggleAudioMuted,
25
+        toggleVideo: APP.conference.toggleVideoMuted,
28 26
         toggleFilmStrip: APP.UI.toggleFilmStrip,
29 27
         toggleChat: APP.UI.toggleChat,
30 28
         toggleContactList: APP.UI.toggleContactList
@@ -43,7 +41,7 @@ function initCommands() {
43 41
  *              participantLeft: boolean
44 42
  *      }}
45 43
  */
46
-var events = {
44
+const events = {
47 45
     incomingMessage: false,
48 46
     outgoingMessage:false,
49 47
     displayNameChange: false,
@@ -51,8 +49,6 @@ var events = {
51 49
     participantLeft: false
52 50
 };
53 51
 
54
-var displayName = {};
55
-
56 52
 /**
57 53
  * Processes commands from external application.
58 54
  * @param message the object with the command
@@ -128,44 +124,42 @@ function processMessage(event) {
128 124
     }
129 125
 }
130 126
 
131
-function setupListeners() {
132
-    APP.xmpp.addListener(XMPPEvents.MUC_MEMBER_JOINED, function (from) {
133
-        API.triggerEvent("participantJoined", {jid: from});
134
-    });
135
-    APP.xmpp.addListener(XMPPEvents.MESSAGE_RECEIVED,
136
-                         function (from, nick, txt, myjid, stamp) {
137
-        if (from != myjid)
138
-            API.triggerEvent("incomingMessage",
139
-                {"from": from, "nick": nick, "message": txt, "stamp": stamp});
140
-    });
141
-    APP.xmpp.addListener(XMPPEvents.MUC_MEMBER_LEFT, function (jid) {
142
-        API.triggerEvent("participantLeft", {jid: jid});
143
-    });
144
-    APP.xmpp.addListener(XMPPEvents.DISPLAY_NAME_CHANGED,
145
-                         function (jid, newDisplayName) {
146
-        var name = displayName[jid];
147
-        if(!name || name != newDisplayName) {
148
-            API.triggerEvent("displayNameChange",
149
-                             {jid: jid, displayname: newDisplayName});
150
-            displayName[jid] = newDisplayName;
151
-        }
152
-    });
153
-    APP.xmpp.addListener(XMPPEvents.SENDING_CHAT_MESSAGE, function (body) {
154
-        APP.API.triggerEvent("outgoingMessage", {"message": body});
155
-    });
127
+/**
128
+ * Check whether the API should be enabled or not.
129
+ * @returns {boolean}
130
+ */
131
+function isEnabled () {
132
+    let hash = location.hash;
133
+    return hash && hash.indexOf("external") > -1 && window.postMessage;
156 134
 }
157 135
 
158
-var API = {
159
-    /**
160
-     * Check whether the API should be enabled or not.
161
-     * @returns {boolean}
162
-     */
163
-    isEnabled: function () {
164
-        var hash = location.hash;
165
-        if (hash && hash.indexOf("external") > -1 && window.postMessage)
166
-            return true;
167
-        return false;
168
-    },
136
+/**
137
+ * Checks whether the event is enabled ot not.
138
+ * @param name the name of the event.
139
+ * @returns {*}
140
+ */
141
+function isEventEnabled (name) {
142
+    return events[name];
143
+}
144
+
145
+/**
146
+ * Sends event object to the external application that has been subscribed
147
+ * for that event.
148
+ * @param name the name event
149
+ * @param object data associated with the event
150
+ */
151
+function triggerEvent (name, object) {
152
+    if (isEnabled() && isEventEnabled(name)) {
153
+        sendMessage({
154
+            type: "event",
155
+            action: "result",
156
+            event: name,
157
+            result: object
158
+        });
159
+    }
160
+}
161
+
162
+export default {
169 163
     /**
170 164
      * Initializes the APIConnector. Setups message event listeners that will
171 165
      * receive information from external applications that embed Jitsi Meet.
@@ -173,50 +167,85 @@ var API = {
173 167
      * is initialized.
174 168
      */
175 169
     init: function () {
170
+        if (!isEnabled()) {
171
+            return;
172
+        }
176 173
         initCommands();
177 174
         if (window.addEventListener) {
178
-            window.addEventListener('message',
179
-                processMessage, false);
180
-        }
181
-        else {
175
+            window.addEventListener('message', processMessage, false);
176
+        } else {
182 177
             window.attachEvent('onmessage', processMessage);
183 178
         }
184 179
         sendMessage({type: "system", loaded: true});
185
-        setupListeners();
186 180
     },
181
+
182
+    /**
183
+     * Notify external application (if API is enabled) that message was sent.
184
+     * @param {string} body message body
185
+     */
186
+    notifySendingChatMessage (body) {
187
+        triggerEvent("outgoingMessage", {"message": body});
188
+    },
189
+
187 190
     /**
188
-     * Checks whether the event is enabled ot not.
189
-     * @param name the name of the event.
190
-     * @returns {*}
191
+     * Notify external application (if API is enabled) that
192
+     * message was received.
193
+     * @param {string} id user id
194
+     * @param {string} nick user nickname
195
+     * @param {string} body message body
196
+     * @param {number} ts message creation timestamp
191 197
      */
192
-    isEventEnabled: function (name) {
193
-        return events[name];
198
+    notifyReceivedChatMessage (id, nick, body, ts) {
199
+        if (APP.conference.isLocalId(id)) {
200
+            return;
201
+        }
202
+
203
+        triggerEvent(
204
+            "incomingMessage",
205
+            {"from": id, "nick": nick, "message": body, "stamp": ts}
206
+        );
194 207
     },
195 208
 
196 209
     /**
197
-     * Sends event object to the external application that has been subscribed
198
-     * for that event.
199
-     * @param name the name event
200
-     * @param object data associated with the event
210
+     * Notify external application (if API is enabled) that
211
+     * user joined the conference.
212
+     * @param {string} id user id
201 213
      */
202
-    triggerEvent: function (name, object) {
203
-        if(this.isEnabled() && this.isEventEnabled(name))
204
-            sendMessage({
205
-                type: "event", action: "result", event: name, result: object});
214
+    notifyUserJoined (id) {
215
+        triggerEvent("participantJoined", {id});
216
+    },
217
+
218
+    /**
219
+     * Notify external application (if API is enabled) that
220
+     * user left the conference.
221
+     * @param {string} id user id
222
+     */
223
+    notifyUserLeft (id) {
224
+        triggerEvent("participantLeft", {id});
225
+    },
226
+
227
+    /**
228
+     * Notify external application (if API is enabled) that
229
+     * user changed their nickname.
230
+     * @param {string} id user id
231
+     * @param {string} displayName user nickname
232
+     */
233
+    notifyDisplayNameChanged (id, displayName) {
234
+        triggerEvent("displayNameChange", {id, displayname: displayName});
206 235
     },
207 236
 
208 237
     /**
209 238
      * Removes the listeners.
210 239
      */
211 240
     dispose: function () {
212
-        if(window.removeEventListener) {
213
-            window.removeEventListener("message",
214
-                processMessage, false);
241
+        if (!isEnabled()) {
242
+            return;
215 243
         }
216
-        else {
244
+
245
+        if (window.removeEventListener) {
246
+            window.removeEventListener("message", processMessage, false);
247
+        } else {
217 248
             window.detachEvent('onmessage', processMessage);
218 249
         }
219 250
     }
220 251
 };
221
-
222
-module.exports = API;

+ 0
- 47
modules/DTMF/DTMF.js 查看文件

@@ -1,47 +0,0 @@
1
-/* global APP */
2
-
3
-/**
4
- * A module for sending DTMF tones.
5
- */
6
-var DTMFSender;
7
-var initDtmfSender = function() {
8
-    // TODO: This needs to reset this if the peerconnection changes
9
-    // (e.g. the call is re-made)
10
-    if (DTMFSender)
11
-        return;
12
-
13
-    var localAudio = APP.RTC.localAudio;
14
-    if (localAudio && localAudio.getTracks().length > 0)
15
-    {
16
-        var peerconnection
17
-            = APP.xmpp.getConnection().jingle.activecall.peerconnection;
18
-        if (peerconnection) {
19
-            DTMFSender =
20
-                peerconnection.peerconnection
21
-                    .createDTMFSender(localAudio.getTracks()[0]);
22
-            console.log("Initialized DTMFSender");
23
-        }
24
-        else {
25
-            console.log("Failed to initialize DTMFSender: no PeerConnection.");
26
-        }
27
-    }
28
-    else {
29
-        console.log("Failed to initialize DTMFSender: no audio track.");
30
-    }
31
-};
32
-
33
-var DTMF = {
34
-    sendTones: function (tones, duration, pause) {
35
-        if (!DTMFSender)
36
-            initDtmfSender();
37
-
38
-        if (DTMFSender){
39
-            DTMFSender.insertDTMF(tones,
40
-                                  (duration || 200),
41
-                                  (pause || 200));
42
-        }
43
-    }
44
-};
45
-
46
-module.exports = DTMF;
47
-

+ 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(RTCBrowserType.isTemasysPluginUsed()) {
304
-            // themasys
305
-            mediaStream.attachEvent('ended', function () {
306
-                handler(mediaStream);
307
-            });
308
-        }
309
-        else {
310
-            if(typeof mediaStream.active !== "undefined")
311
-                mediaStream.oninactive = handler;
312
-            else
313
-                mediaStream.onended = handler;
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(RTCBrowserType.isTemasysPluginUsed()) {
323
-            // themasys
324
-            mediaStream.detachEvent('ended', handler);
325
-        }
326
-        else {
327
-            if(typeof mediaStream.active !== "undefined")
328
-                mediaStream.oninactive = null;
329
-            else
330
-                mediaStream.onended = null;
331
-        }
332
-    }
333
-};
334
-
335
-module.exports = RTC;

+ 0
- 576
modules/RTC/RTCUtils.js 查看文件

@@ -1,576 +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
-        // seems to be fixed now, removing this experimental fix, as having
150
-        // multiple audio tracks brake the tests
151
-        //constraints.audio = true;
152
-        constraints.fake = true;
153
-    }
154
-
155
-    return constraints;
156
-}
157
-
158
-
159
-function RTCUtils(RTCService, eventEmitter, onTemasysPluginReady)
160
-{
161
-    var self = this;
162
-    this.service = RTCService;
163
-    this.eventEmitter = eventEmitter;
164
-    if (RTCBrowserType.isFirefox()) {
165
-        var FFversion = RTCBrowserType.getFirefoxVersion();
166
-        if (FFversion >= 40) {
167
-            this.peerconnection = mozRTCPeerConnection;
168
-            this.getUserMedia = navigator.mozGetUserMedia.bind(navigator);
169
-            this.pc_constraints = {};
170
-            this.attachMediaStream =  function (element, stream) {
171
-                //  srcObject is being standardized and FF will eventually
172
-                //  support that unprefixed. FF also supports the
173
-                //  "element.src = URL.createObjectURL(...)" combo, but that
174
-                //  will be deprecated in favour of srcObject.
175
-                //
176
-                // https://groups.google.com/forum/#!topic/mozilla.dev.media/pKOiioXonJg
177
-                // https://github.com/webrtc/samples/issues/302
178
-                if(!element[0])
179
-                    return;
180
-                element[0].mozSrcObject = stream;
181
-                element[0].play();
182
-            };
183
-            this.getStreamID =  function (stream) {
184
-                var id = stream.id;
185
-                if (!id) {
186
-                    var tracks = stream.getVideoTracks();
187
-                    if (!tracks || tracks.length === 0) {
188
-                        tracks = stream.getAudioTracks();
189
-                    }
190
-                    id = tracks[0].id;
191
-                }
192
-                return APP.xmpp.filter_special_chars(id);
193
-            };
194
-            this.getVideoSrc = function (element) {
195
-                if(!element)
196
-                    return null;
197
-                return element.mozSrcObject;
198
-            };
199
-            this.setVideoSrc = function (element, src) {
200
-                if(element)
201
-                    element.mozSrcObject = src;
202
-            };
203
-            window.RTCSessionDescription = mozRTCSessionDescription;
204
-            window.RTCIceCandidate = mozRTCIceCandidate;
205
-        } else {
206
-            console.error(
207
-                "Firefox version too old: " + FFversion + ". Required >= 40.");
208
-            window.location.href = 'unsupported_browser.html';
209
-            return;
210
-        }
211
-
212
-    } else if (RTCBrowserType.isChrome() || RTCBrowserType.isOpera()) {
213
-        this.peerconnection = webkitRTCPeerConnection;
214
-        this.getUserMedia = navigator.webkitGetUserMedia.bind(navigator);
215
-        this.attachMediaStream = function (element, stream) {
216
-            element.attr('src', webkitURL.createObjectURL(stream));
217
-        };
218
-        this.getStreamID = function (stream) {
219
-            // streams from FF endpoints have the characters '{' and '}'
220
-            // that make jQuery choke.
221
-            return APP.xmpp.filter_special_chars(stream.id);
222
-        };
223
-        this.getVideoSrc = function (element) {
224
-            if(!element)
225
-                return null;
226
-            return element.getAttribute("src");
227
-        };
228
-        this.setVideoSrc = function (element, src) {
229
-            if(element)
230
-                element.setAttribute("src", src);
231
-        };
232
-        // DTLS should now be enabled by default but..
233
-        this.pc_constraints = {'optional': [{'DtlsSrtpKeyAgreement': 'true'}]};
234
-        if (RTCBrowserType.isAndroid()) {
235
-            this.pc_constraints = {}; // disable DTLS on Android
236
-        }
237
-        if (!webkitMediaStream.prototype.getVideoTracks) {
238
-            webkitMediaStream.prototype.getVideoTracks = function () {
239
-                return this.videoTracks;
240
-            };
241
-        }
242
-        if (!webkitMediaStream.prototype.getAudioTracks) {
243
-            webkitMediaStream.prototype.getAudioTracks = function () {
244
-                return this.audioTracks;
245
-            };
246
-        }
247
-    }
248
-    // Detect IE/Safari
249
-    else if (RTCBrowserType.isTemasysPluginUsed()) {
250
-
251
-        //AdapterJS.WebRTCPlugin.setLogLevel(
252
-        //    AdapterJS.WebRTCPlugin.PLUGIN_LOG_LEVELS.VERBOSE);
253
-
254
-        AdapterJS.webRTCReady(function (isPlugin) {
255
-
256
-            self.peerconnection = RTCPeerConnection;
257
-            self.getUserMedia = getUserMedia;
258
-            self.attachMediaStream = function (elSel, stream) {
259
-
260
-                if (stream.id === "dummyAudio" || stream.id === "dummyVideo") {
261
-                    return;
262
-                }
263
-
264
-                attachMediaStream(elSel[0], stream);
265
-            };
266
-            self.getStreamID = function (stream) {
267
-                return APP.xmpp.filter_special_chars(stream.label);
268
-            };
269
-            self.getVideoSrc = function (element) {
270
-                if (!element) {
271
-                    console.warn("Attempt to get video SRC of null element");
272
-                    return null;
273
-                }
274
-                var children = element.children;
275
-                for (var i = 0; i !== children.length; ++i) {
276
-                    if (children[i].name === 'streamId') {
277
-                        return children[i].value;
278
-                    }
279
-                }
280
-                //console.info(element.id + " SRC: " + src);
281
-                return null;
282
-            };
283
-            self.setVideoSrc = function (element, src) {
284
-                //console.info("Set video src: ", element, src);
285
-                if (!src) {
286
-                    console.warn("Not attaching video stream, 'src' is null");
287
-                    return;
288
-                }
289
-                AdapterJS.WebRTCPlugin.WaitForPluginReady();
290
-                var stream = AdapterJS.WebRTCPlugin.plugin
291
-                    .getStreamWithId(AdapterJS.WebRTCPlugin.pageId, src);
292
-                attachMediaStream(element, stream);
293
-            };
294
-
295
-            onTemasysPluginReady(isPlugin);
296
-        });
297
-    } else {
298
-        try {
299
-            console.log('Browser does not appear to be WebRTC-capable');
300
-        } catch (e) { }
301
-        window.location.href = 'unsupported_browser.html';
302
-    }
303
-}
304
-
305
-
306
-RTCUtils.prototype.getUserMediaWithConstraints = function(
307
-    um, success_callback, failure_callback, resolution,bandwidth, fps,
308
-    desktopStream) {
309
-    currentResolution = resolution;
310
-
311
-    var constraints = getConstraints(
312
-        um, resolution, bandwidth, fps, desktopStream);
313
-
314
-    console.info("Get media constraints", constraints);
315
-
316
-    var self = this;
317
-
318
-    try {
319
-        this.getUserMedia(constraints,
320
-            function (stream) {
321
-                console.log('onUserMediaSuccess');
322
-                self.setAvailableDevices(um, true);
323
-                success_callback(stream);
324
-            },
325
-            function (error) {
326
-                self.setAvailableDevices(um, false);
327
-                console.warn('Failed to get access to local media. Error ',
328
-                    error, constraints);
329
-                self.eventEmitter.emit(RTCEvents.GET_USER_MEDIA_FAILED, error);
330
-                if (failure_callback) {
331
-                    failure_callback(error);
332
-                }
333
-            });
334
-    } catch (e) {
335
-        console.error('GUM failed: ', e);
336
-        self.eventEmitter.emit(RTCEvents.GET_USER_MEDIA_FAILED, e);
337
-        if(failure_callback) {
338
-            failure_callback(e);
339
-        }
340
-    }
341
-};
342
-
343
-RTCUtils.prototype.setAvailableDevices = function (um, available) {
344
-    var devices = {};
345
-    if(um.indexOf("video") != -1) {
346
-        devices.video = available;
347
-    }
348
-    if(um.indexOf("audio") != -1) {
349
-        devices.audio = available;
350
-    }
351
-    this.service.setDeviceAvailability(devices);
352
-};
353
-
354
-/**
355
- * We ask for audio and video combined stream in order to get permissions and
356
- * not to ask twice.
357
- */
358
-RTCUtils.prototype.obtainAudioAndVideoPermissions =
359
-    function(devices, callback, usageOptions)
360
-{
361
-    var self = this;
362
-    // Get AV
363
-
364
-    var successCallback = function (stream) {
365
-        if(callback)
366
-            callback(stream, usageOptions);
367
-        else
368
-            self.successCallback(stream, usageOptions);
369
-    };
370
-
371
-    if(!devices)
372
-        devices = ['audio', 'video'];
373
-
374
-    var newDevices = [];
375
-
376
-
377
-    if(usageOptions)
378
-        for(var i = 0; i < devices.length; i++) {
379
-            var device = devices[i];
380
-            if(usageOptions[device] === true)
381
-                newDevices.push(device);
382
-        }
383
-    else
384
-        newDevices = devices;
385
-
386
-    if(newDevices.length === 0) {
387
-        successCallback();
388
-        return;
389
-    }
390
-
391
-    if (RTCBrowserType.isFirefox() || RTCBrowserType.isTemasysPluginUsed()) {
392
-
393
-        // With FF/IE we can't split the stream into audio and video because FF
394
-        // doesn't support media stream constructors. So, we need to get the
395
-        // audio stream separately from the video stream using two distinct GUM
396
-        // calls. Not very user friendly :-( but we don't have many other
397
-        // options neither.
398
-        //
399
-        // Note that we pack those 2 streams in a single object and pass it to
400
-        // the successCallback method.
401
-        var obtainVideo = function (audioStream) {
402
-            self.getUserMediaWithConstraints(
403
-                ['video'],
404
-                function (videoStream) {
405
-                    return successCallback({
406
-                        audioStream: audioStream,
407
-                        videoStream: videoStream
408
-                    });
409
-                },
410
-                function (error) {
411
-                    console.error(
412
-                        'failed to obtain video stream - stop', error);
413
-                    self.errorCallback(error);
414
-                },
415
-                config.resolution || '360');
416
-        };
417
-        var obtainAudio = function () {
418
-            self.getUserMediaWithConstraints(
419
-                ['audio'],
420
-                function (audioStream) {
421
-                    if (newDevices.indexOf('video') !== -1)
422
-                        obtainVideo(audioStream);
423
-                },
424
-                function (error) {
425
-                    console.error(
426
-                        'failed to obtain audio stream - stop', error);
427
-                    self.errorCallback(error);
428
-                }
429
-            );
430
-        };
431
-        if (newDevices.indexOf('audio') !== -1) {
432
-            obtainAudio();
433
-        } else {
434
-            obtainVideo(null);
435
-        }
436
-    } else {
437
-        this.getUserMediaWithConstraints(
438
-        newDevices,
439
-        function (stream) {
440
-            successCallback(stream);
441
-        },
442
-        function (error) {
443
-            self.errorCallback(error);
444
-        },
445
-        config.resolution || '360');
446
-    }
447
-};
448
-
449
-RTCUtils.prototype.successCallback = function (stream, usageOptions) {
450
-    // If this is FF or IE, the stream parameter is *not* a MediaStream object,
451
-    // it's an object with two properties: audioStream, videoStream.
452
-    if (stream && stream.getAudioTracks && stream.getVideoTracks)
453
-        console.log('got', stream, stream.getAudioTracks().length,
454
-            stream.getVideoTracks().length);
455
-    this.handleLocalStream(stream, usageOptions);
456
-};
457
-
458
-RTCUtils.prototype.errorCallback = function (error) {
459
-    var self = this;
460
-    console.error('failed to obtain audio/video stream - trying audio only', error);
461
-    var resolution = getPreviousResolution(currentResolution);
462
-    if(typeof error == "object" && error.constraintName && error.name
463
-        && (error.name == "ConstraintNotSatisfiedError" ||
464
-            error.name == "OverconstrainedError") &&
465
-        (error.constraintName == "minWidth" || error.constraintName == "maxWidth" ||
466
-            error.constraintName == "minHeight" || error.constraintName == "maxHeight")
467
-        && resolution)
468
-    {
469
-        self.getUserMediaWithConstraints(['audio', 'video'],
470
-            function (stream) {
471
-                return self.successCallback(stream);
472
-            }, function (error) {
473
-                return self.errorCallback(error);
474
-            }, resolution);
475
-    }
476
-    else {
477
-        self.getUserMediaWithConstraints(
478
-            ['audio'],
479
-            function (stream) {
480
-                return self.successCallback(stream);
481
-            },
482
-            function (error) {
483
-                console.error('failed to obtain audio/video stream - stop',
484
-                    error);
485
-                return self.successCallback(null);
486
-            }
487
-        );
488
-    }
489
-};
490
-
491
-RTCUtils.prototype.handleLocalStream = function(stream, usageOptions) {
492
-    // If this is FF, the stream parameter is *not* a MediaStream object, it's
493
-    // an object with two properties: audioStream, videoStream.
494
-    var audioStream, videoStream;
495
-    if(window.webkitMediaStream)
496
-    {
497
-        audioStream = new webkitMediaStream();
498
-        videoStream = new webkitMediaStream();
499
-        if(stream) {
500
-            var audioTracks = stream.getAudioTracks();
501
-
502
-            for (var i = 0; i < audioTracks.length; i++) {
503
-                audioStream.addTrack(audioTracks[i]);
504
-            }
505
-
506
-            var videoTracks = stream.getVideoTracks();
507
-
508
-            for (i = 0; i < videoTracks.length; i++) {
509
-                videoStream.addTrack(videoTracks[i]);
510
-            }
511
-        }
512
-    }
513
-    else if (RTCBrowserType.isFirefox() || RTCBrowserType.isTemasysPluginUsed())
514
-    {   // Firefox and Temasys plugin
515
-        if (stream && stream.audioStream)
516
-            audioStream = stream.audioStream;
517
-        else
518
-            audioStream = new DummyMediaStream("dummyAudio");
519
-
520
-        if (stream && stream.videoStream)
521
-            videoStream = stream.videoStream;
522
-        else
523
-            videoStream = new DummyMediaStream("dummyVideo");
524
-    }
525
-
526
-    var audioMuted = (usageOptions && usageOptions.audio === false),
527
-        videoMuted = (usageOptions && usageOptions.video === false);
528
-
529
-    var audioGUM = (!usageOptions || usageOptions.audio !== false),
530
-        videoGUM = (!usageOptions || usageOptions.video !== false);
531
-
532
-
533
-    this.service.createLocalStream(
534
-        audioStream, MediaStreamType.AUDIO_TYPE, null, null,
535
-        audioMuted, audioGUM);
536
-
537
-    this.service.createLocalStream(
538
-        videoStream, MediaStreamType.VIDEO_TYPE, null, 'camera',
539
-        videoMuted, videoGUM);
540
-};
541
-
542
-function DummyMediaStream(id) {
543
-    this.id = id;
544
-    this.label = id;
545
-    this.stop = function() { };
546
-    this.getAudioTracks = function() { return []; };
547
-    this.getVideoTracks = function() { return []; };
548
-}
549
-
550
-RTCUtils.prototype.createStream = function(stream, isVideo) {
551
-    var newStream = null;
552
-    if (window.webkitMediaStream) {
553
-        newStream = new webkitMediaStream();
554
-        if (newStream) {
555
-            var tracks = (isVideo ? stream.getVideoTracks() : stream.getAudioTracks());
556
-
557
-            for (var i = 0; i < tracks.length; i++) {
558
-                newStream.addTrack(tracks[i]);
559
-            }
560
-        }
561
-
562
-    }
563
-    else {
564
-        // FIXME: this is duplicated with 'handleLocalStream' !!!
565
-        if (stream) {
566
-            newStream = stream;
567
-        } else {
568
-            newStream =
569
-                new DummyMediaStream(isVideo ? "dummyVideo" : "dummyAudio");
570
-        }
571
-    }
572
-
573
-    return newStream;
574
-};
575
-
576
-module.exports = RTCUtils;

+ 0
- 1168
modules/RTC/adapter.screenshare.js
文件差异内容过多而无法显示
查看文件


+ 5
- 10
modules/UI/Feedback.js 查看文件

@@ -1,11 +1,9 @@
1
-/* global $, config, interfaceConfig */
1
+/* global $, APP, config, interfaceConfig */
2 2
 
3 3
 /*
4 4
  * Created by Yana Stamcheva on 2/10/15.
5 5
  */
6 6
 var messageHandler = require("./util/MessageHandler");
7
-var callStats = require("../statistics/CallStats");
8
-var APP = require("../../app");
9 7
 
10 8
 /**
11 9
  * Constructs the html for the overall feedback window.
@@ -79,7 +77,7 @@ var Feedback = {
79 77
     init: function () {
80 78
         // CallStats is the way we send feedback, so we don't have to initialise
81 79
         // if callstats isn't enabled.
82
-        if (!this.isEnabled())
80
+        if (!APP.conference.isCallstatsEnabled())
83 81
             return;
84 82
 
85 83
         $("div.feedbackButton").css("display", "block");
@@ -93,10 +91,7 @@ var Feedback = {
93 91
      * @return true if the feedback functionality is enabled, false otherwise.
94 92
      */
95 93
     isEnabled: function() {
96
-        // XXX callStats.isEnabled() indicates whether we are allowed to attempt
97
-        // to integrate callstats.io. Whether our attempt was/is/will be
98
-        // successful is a different question.
99
-        return callStats.isEnabled();
94
+        return APP.conference.isCallstatsEnabled();
100 95
     },
101 96
     /**
102 97
      * Opens the feedback window.
@@ -122,7 +117,7 @@ var Feedback = {
122 117
                     // If the feedback is less than 3 stars we're going to
123 118
                     // ask the user for more information.
124 119
                     if (Feedback.feedbackScore > 3) {
125
-                        callStats.sendFeedback(Feedback.feedbackScore, "");
120
+                        APP.conference.sendFeedback(Feedback.feedbackScore, "");
126 121
                         if (feedbackWindowCallback)
127 122
                             feedbackWindowCallback();
128 123
                         else
@@ -164,7 +159,7 @@ var Feedback = {
164 159
                             = document.getElementById("feedbackTextArea").value;
165 160
 
166 161
                         if (feedbackDetails && feedbackDetails.length > 0)
167
-                            callStats.sendFeedback( Feedback.feedbackScore,
162
+                            APP.conference.sendFeedback( Feedback.feedbackScore,
168 163
                                                     feedbackDetails);
169 164
 
170 165
                         if (feedbackWindowCallback)

+ 753
- 624
modules/UI/UI.js
文件差异内容过多而无法显示
查看文件


+ 158
- 189
modules/UI/audio_levels/AudioLevels.js 查看文件

@@ -1,12 +1,17 @@
1
-/* global APP, interfaceConfig, $, Strophe */
1
+/* global APP, interfaceConfig, $ */
2 2
 /* jshint -W101 */
3
-var CanvasUtil = require("./CanvasUtils");
4 3
 
5
-var ASDrawContext = null;
4
+import CanvasUtil from './CanvasUtils';
5
+import BottomToolbar from '../toolbars/BottomToolbar';
6 6
 
7
-function initActiveSpeakerAudioLevels() {
8
-    var ASRadius = interfaceConfig.ACTIVE_SPEAKER_AVATAR_SIZE / 2;
9
-    var ASCenter = (interfaceConfig.ACTIVE_SPEAKER_AVATAR_SIZE + ASRadius) / 2;
7
+const LOCAL_LEVEL = 'local';
8
+
9
+let ASDrawContext = null;
10
+let audioLevelCanvasCache = {};
11
+
12
+function initDominantSpeakerAudioLevels() {
13
+    let ASRadius = interfaceConfig.DOMINANT_SPEAKER_AVATAR_SIZE / 2;
14
+    let ASCenter = (interfaceConfig.DOMINANT_SPEAKER_AVATAR_SIZE + ASRadius) / 2;
10 15
 
11 16
     // Draw a circle.
12 17
     ASDrawContext.arc(ASCenter, ASCenter, ASRadius, 0, 2 * Math.PI);
@@ -18,249 +23,213 @@ function initActiveSpeakerAudioLevels() {
18 23
 }
19 24
 
20 25
 /**
21
- * The audio Levels plugin.
26
+ * Resizes the given audio level canvas to match the given thumbnail size.
27
+ */
28
+function resizeAudioLevelCanvas(audioLevelCanvas, thumbnailWidth, thumbnailHeight) {
29
+    audioLevelCanvas.width = thumbnailWidth + interfaceConfig.CANVAS_EXTRA;
30
+    audioLevelCanvas.height = thumbnailHeight + interfaceConfig.CANVAS_EXTRA;
31
+}
32
+
33
+/**
34
+ * Draws the audio level canvas into the cached canvas object.
35
+ *
36
+ * @param id of the user for whom we draw the audio level
37
+ * @param audioLevel the newAudio level to render
38
+ */
39
+function drawAudioLevelCanvas(id, audioLevel) {
40
+    if (!audioLevelCanvasCache[id]) {
41
+
42
+        let videoSpanId = getVideoSpanId(id);
43
+
44
+        let audioLevelCanvasOrig = $(`#${videoSpanId}>canvas`).get(0);
45
+
46
+        /*
47
+         * FIXME Testing has shown that audioLevelCanvasOrig may not exist.
48
+         * In such a case, the method CanvasUtil.cloneCanvas may throw an
49
+         * error. Since audio levels are frequently updated, the errors have
50
+         * been observed to pile into the console, strain the CPU.
51
+         */
52
+        if (audioLevelCanvasOrig) {
53
+            audioLevelCanvasCache[id] = CanvasUtil.cloneCanvas(audioLevelCanvasOrig);
54
+        }
55
+    }
56
+
57
+    let canvas = audioLevelCanvasCache[id];
58
+
59
+    if (!canvas) {
60
+        return;
61
+    }
62
+
63
+    let drawContext = canvas.getContext('2d');
64
+
65
+    drawContext.clearRect(0, 0, canvas.width, canvas.height);
66
+
67
+    let shadowLevel = getShadowLevel(audioLevel);
68
+
69
+    if (shadowLevel > 0) {
70
+        // drawContext, x, y, w, h, r, shadowColor, shadowLevel
71
+        CanvasUtil.drawRoundRectGlow(drawContext,
72
+                                     interfaceConfig.CANVAS_EXTRA / 2, interfaceConfig.CANVAS_EXTRA / 2,
73
+                                     canvas.width - interfaceConfig.CANVAS_EXTRA,
74
+                                     canvas.height - interfaceConfig.CANVAS_EXTRA,
75
+                                     interfaceConfig.CANVAS_RADIUS,
76
+                                     interfaceConfig.SHADOW_COLOR,
77
+                                     shadowLevel);
78
+    }
79
+}
80
+
81
+/**
82
+ * Returns the shadow/glow level for the given audio level.
83
+ *
84
+ * @param audioLevel the audio level from which we determine the shadow
85
+ * level
22 86
  */
23
-var AudioLevels = (function(my) {
24
-    var audioLevelCanvasCache = {};
87
+function getShadowLevel (audioLevel) {
88
+    let shadowLevel = 0;
89
+
90
+    if (audioLevel <= 0.3) {
91
+        shadowLevel = Math.round(interfaceConfig.CANVAS_EXTRA/2*(audioLevel/0.3));
92
+    } else if (audioLevel <= 0.6) {
93
+        shadowLevel = Math.round(interfaceConfig.CANVAS_EXTRA/2*((audioLevel - 0.3) / 0.3));
94
+    } else {
95
+        shadowLevel = Math.round(interfaceConfig.CANVAS_EXTRA/2*((audioLevel - 0.6) / 0.4));
96
+    }
25 97
 
26
-    my.LOCAL_LEVEL = 'local';
98
+    return shadowLevel;
99
+}
27 100
 
28
-    my.init = function () {
29
-        ASDrawContext = $('#activeSpeakerAudioLevel')[0].getContext('2d');
30
-        initActiveSpeakerAudioLevels();
31
-    };
101
+/**
102
+ * Returns the video span id corresponding to the given user id
103
+ */
104
+function getVideoSpanId(id) {
105
+    let videoSpanId = null;
106
+
107
+    if (id === LOCAL_LEVEL || APP.conference.isLocalId(id)) {
108
+        videoSpanId = 'localVideoContainer';
109
+    } else {
110
+        videoSpanId = `participant_${id}`;
111
+    }
112
+
113
+    return videoSpanId;
114
+}
115
+
116
+/**
117
+ * The audio Levels plugin.
118
+ */
119
+const AudioLevels = {
120
+
121
+    init () {
122
+        ASDrawContext = $('#dominantSpeakerAudioLevel')[0].getContext('2d');
123
+        initDominantSpeakerAudioLevels();
124
+    },
32 125
 
33 126
     /**
34
-     * Updates the audio level canvas for the given peerJid. If the canvas
127
+     * Updates the audio level canvas for the given id. If the canvas
35 128
      * didn't exist we create it.
36 129
      */
37
-    my.updateAudioLevelCanvas = function (peerJid, VideoLayout) {
38
-        var resourceJid = null;
39
-        var videoSpanId = null;
40
-        if (!peerJid)
41
-            videoSpanId = 'localVideoContainer';
42
-        else {
43
-            resourceJid = Strophe.getResourceFromJid(peerJid);
44
-
45
-            videoSpanId = 'participant_' + resourceJid;
130
+    updateAudioLevelCanvas (id, thumbWidth, thumbHeight) {
131
+        let videoSpanId = 'localVideoContainer';
132
+        if (id) {
133
+            videoSpanId = `participant_${id}`;
46 134
         }
47 135
 
48
-        var videoSpan = document.getElementById(videoSpanId);
136
+        let videoSpan = document.getElementById(videoSpanId);
49 137
 
50 138
         if (!videoSpan) {
51
-            if (resourceJid)
52
-                console.error("No video element for jid", resourceJid);
53
-            else
139
+            if (id) {
140
+                console.error("No video element for id", id);
141
+            } else {
54 142
                 console.error("No video element for local video.");
55
-
143
+            }
56 144
             return;
57 145
         }
58 146
 
59
-        var audioLevelCanvas = $('#' + videoSpanId + '>canvas');
60
-
61
-        var videoSpaceWidth = $('#remoteVideos').width();
62
-        var thumbnailSize = VideoLayout.calculateThumbnailSize(videoSpaceWidth);
63
-        var thumbnailWidth = thumbnailSize[0];
64
-        var thumbnailHeight = thumbnailSize[1];
147
+        let audioLevelCanvas = $(`#${videoSpanId}>canvas`);
65 148
 
66 149
         if (!audioLevelCanvas || audioLevelCanvas.length === 0) {
67 150
 
68 151
             audioLevelCanvas = document.createElement('canvas');
69 152
             audioLevelCanvas.className = "audiolevel";
70
-            audioLevelCanvas.style.bottom = "-" + interfaceConfig.CANVAS_EXTRA/2 + "px";
71
-            audioLevelCanvas.style.left = "-" + interfaceConfig.CANVAS_EXTRA/2 + "px";
72
-            resizeAudioLevelCanvas( audioLevelCanvas,
73
-                    thumbnailWidth,
74
-                    thumbnailHeight);
153
+            audioLevelCanvas.style.bottom = `-${interfaceConfig.CANVAS_EXTRA/2}px`;
154
+            audioLevelCanvas.style.left = `-${interfaceConfig.CANVAS_EXTRA/2}px`;
155
+            resizeAudioLevelCanvas(audioLevelCanvas, thumbWidth, thumbHeight);
75 156
 
76 157
             videoSpan.appendChild(audioLevelCanvas);
77 158
         } else {
78 159
             audioLevelCanvas = audioLevelCanvas.get(0);
79 160
 
80
-            resizeAudioLevelCanvas( audioLevelCanvas,
81
-                    thumbnailWidth,
82
-                    thumbnailHeight);
161
+            resizeAudioLevelCanvas(audioLevelCanvas, thumbWidth, thumbHeight);
83 162
         }
84
-    };
163
+    },
85 164
 
86 165
     /**
87
-     * Updates the audio level UI for the given resourceJid.
166
+     * Updates the audio level UI for the given id.
88 167
      *
89
-     * @param resourceJid the resource jid indicating the video element for
90
-     * which we draw the audio level
168
+     * @param id id of the user for whom we draw the audio level
91 169
      * @param audioLevel the newAudio level to render
92 170
      */
93
-    my.updateAudioLevel = function (resourceJid, audioLevel, largeVideoResourceJid) {
94
-        drawAudioLevelCanvas(resourceJid, audioLevel);
171
+    updateAudioLevel (id, audioLevel, largeVideoId) {
172
+        drawAudioLevelCanvas(id, audioLevel);
95 173
 
96
-        var videoSpanId = getVideoSpanId(resourceJid);
174
+        let videoSpanId = getVideoSpanId(id);
97 175
 
98
-        var audioLevelCanvas = $('#' + videoSpanId + '>canvas').get(0);
176
+        let audioLevelCanvas = $(`#${videoSpanId}>canvas`).get(0);
99 177
 
100
-        if (!audioLevelCanvas)
178
+        if (!audioLevelCanvas) {
101 179
             return;
180
+        }
102 181
 
103
-        var drawContext = audioLevelCanvas.getContext('2d');
182
+        let drawContext = audioLevelCanvas.getContext('2d');
104 183
 
105
-        var canvasCache = audioLevelCanvasCache[resourceJid];
184
+        let canvasCache = audioLevelCanvasCache[id];
106 185
 
107
-        drawContext.clearRect (0, 0,
108
-                audioLevelCanvas.width, audioLevelCanvas.height);
186
+        drawContext.clearRect(
187
+            0, 0, audioLevelCanvas.width, audioLevelCanvas.height
188
+        );
109 189
         drawContext.drawImage(canvasCache, 0, 0);
110 190
 
111
-        if(resourceJid === AudioLevels.LOCAL_LEVEL) {
112
-            if(!APP.xmpp.myJid()) {
191
+        if (id === LOCAL_LEVEL) {
192
+            id = APP.conference.localId;
193
+            if (!id) {
113 194
                 return;
114 195
             }
115
-            resourceJid = APP.xmpp.myResource();
116 196
         }
117 197
 
118
-        if(resourceJid === largeVideoResourceJid) {
198
+        if(id === largeVideoId) {
119 199
             window.requestAnimationFrame(function () {
120
-                AudioLevels.updateActiveSpeakerAudioLevel(audioLevel);
200
+                AudioLevels.updateDominantSpeakerAudioLevel(audioLevel);
121 201
             });
122 202
         }
123
-    };
203
+    },
124 204
 
125
-    my.updateActiveSpeakerAudioLevel = function(audioLevel) {
126
-        if($("#activeSpeaker").css("visibility") == "hidden" || ASDrawContext === null)
205
+    updateDominantSpeakerAudioLevel (audioLevel) {
206
+        if($("#domiantSpeaker").css("visibility") == "hidden" || ASDrawContext === null) {
127 207
             return;
208
+        }
128 209
 
129 210
         ASDrawContext.clearRect(0, 0, 300, 300);
130
-        if (!audioLevel)
211
+        if (!audioLevel) {
131 212
             return;
213
+        }
132 214
 
133 215
         ASDrawContext.shadowBlur = getShadowLevel(audioLevel);
134 216
 
135 217
 
136 218
         // Fill the shape.
137 219
         ASDrawContext.fill();
138
-    };
139
-
140
-    /**
141
-     * Resizes the given audio level canvas to match the given thumbnail size.
142
-     */
143
-    function resizeAudioLevelCanvas(audioLevelCanvas,
144
-                                    thumbnailWidth,
145
-                                    thumbnailHeight) {
146
-        audioLevelCanvas.width = thumbnailWidth + interfaceConfig.CANVAS_EXTRA;
147
-        audioLevelCanvas.height = thumbnailHeight + interfaceConfig.CANVAS_EXTRA;
148
-    }
149
-
150
-    /**
151
-     * Draws the audio level canvas into the cached canvas object.
152
-     *
153
-     * @param resourceJid the resource jid indicating the video element for
154
-     * which we draw the audio level
155
-     * @param audioLevel the newAudio level to render
156
-     */
157
-    function drawAudioLevelCanvas(resourceJid, audioLevel) {
158
-        if (!audioLevelCanvasCache[resourceJid]) {
159
-
160
-            var videoSpanId = getVideoSpanId(resourceJid);
161
-
162
-            var audioLevelCanvasOrig = $('#' + videoSpanId + '>canvas').get(0);
163
-
164
-            /*
165
-             * FIXME Testing has shown that audioLevelCanvasOrig may not exist.
166
-             * In such a case, the method CanvasUtil.cloneCanvas may throw an
167
-             * error. Since audio levels are frequently updated, the errors have
168
-             * been observed to pile into the console, strain the CPU.
169
-             */
170
-            if (audioLevelCanvasOrig) {
171
-                audioLevelCanvasCache[resourceJid] =
172
-                    CanvasUtil.cloneCanvas(audioLevelCanvasOrig);
173
-            }
174
-        }
175
-
176
-        var canvas = audioLevelCanvasCache[resourceJid];
177
-
178
-        if (!canvas)
179
-            return;
180
-
181
-        var drawContext = canvas.getContext('2d');
182
-
183
-        drawContext.clearRect(0, 0, canvas.width, canvas.height);
184
-
185
-        var shadowLevel = getShadowLevel(audioLevel);
186
-
187
-        if (shadowLevel > 0) {
188
-            // drawContext, x, y, w, h, r, shadowColor, shadowLevel
189
-            CanvasUtil.drawRoundRectGlow(drawContext,
190
-                interfaceConfig.CANVAS_EXTRA / 2, interfaceConfig.CANVAS_EXTRA / 2,
191
-                canvas.width - interfaceConfig.CANVAS_EXTRA,
192
-                canvas.height - interfaceConfig.CANVAS_EXTRA,
193
-                interfaceConfig.CANVAS_RADIUS,
194
-                interfaceConfig.SHADOW_COLOR,
195
-                shadowLevel);
196
-        }
197
-    }
220
+    },
198 221
 
199
-    /**
200
-     * Returns the shadow/glow level for the given audio level.
201
-     *
202
-     * @param audioLevel the audio level from which we determine the shadow
203
-     * level
204
-     */
205
-    function getShadowLevel (audioLevel) {
206
-        var shadowLevel = 0;
222
+    updateCanvasSize (thumbWidth, thumbHeight) {
223
+        let canvasWidth = thumbWidth + interfaceConfig.CANVAS_EXTRA;
224
+        let canvasHeight = thumbHeight + interfaceConfig.CANVAS_EXTRA;
207 225
 
208
-        if (audioLevel <= 0.3) {
209
-            shadowLevel = Math.round(interfaceConfig.CANVAS_EXTRA/2*(audioLevel/0.3));
210
-        }
211
-        else if (audioLevel <= 0.6) {
212
-            shadowLevel = Math.round(interfaceConfig.CANVAS_EXTRA/2*((audioLevel - 0.3) / 0.3));
213
-        }
214
-        else {
215
-            shadowLevel = Math.round(interfaceConfig.CANVAS_EXTRA/2*((audioLevel - 0.6) / 0.4));
216
-        }
217
-        return shadowLevel;
218
-    }
219
-
220
-    /**
221
-     * Returns the video span id corresponding to the given resourceJid or local
222
-     * user.
223
-     */
224
-    function getVideoSpanId(resourceJid) {
225
-        var videoSpanId = null;
226
-        if (resourceJid === AudioLevels.LOCAL_LEVEL ||
227
-            (APP.xmpp.myResource() && resourceJid === APP.xmpp.myResource()))
228
-            videoSpanId = 'localVideoContainer';
229
-        else
230
-            videoSpanId = 'participant_' + resourceJid;
231
-
232
-        return videoSpanId;
233
-    }
226
+        BottomToolbar.getThumbs().children('canvas').width(canvasWidth).height(canvasHeight);
234 227
 
235
-    /**
236
-     * Indicates that the remote video has been resized.
237
-     */
238
-    $(document).bind('remotevideo.resized', function (event, width, height) {
239
-        var resized = false;
240
-        $('#remoteVideos>span>canvas').each(function() {
241
-            var canvas = $(this).get(0);
242
-            if (canvas.width !== width + interfaceConfig.CANVAS_EXTRA) {
243
-                canvas.width = width + interfaceConfig.CANVAS_EXTRA;
244
-                resized = true;
245
-            }
246
-
247
-            if (canvas.height !== height + interfaceConfig.CANVAS_EXTRA) {
248
-                canvas.height = height + interfaceConfig.CANVAS_EXTRA;
249
-                resized = true;
250
-            }
228
+        Object.keys(audioLevelCanvasCache).forEach(function (id) {
229
+            audioLevelCanvasCache[id].width = canvasWidth;
230
+            audioLevelCanvasCache[id].height = canvasHeight;
251 231
         });
232
+    }
233
+};
252 234
 
253
-        if (resized)
254
-            Object.keys(audioLevelCanvasCache).forEach(function (resourceJid) {
255
-                audioLevelCanvasCache[resourceJid].width =
256
-                    width + interfaceConfig.CANVAS_EXTRA;
257
-                audioLevelCanvasCache[resourceJid].height =
258
-                    height + interfaceConfig.CANVAS_EXTRA;
259
-            });
260
-    });
261
-
262
-    return my;
263
-
264
-})(AudioLevels || {});
265
-
266
-module.exports = AudioLevels;
235
+export default AudioLevels;

+ 7
- 10
modules/UI/audio_levels/CanvasUtils.js 查看文件

@@ -1,7 +1,7 @@
1 1
 /**
2 2
  * Utility class for drawing canvas shapes.
3 3
  */
4
-var CanvasUtil = (function(my) {
4
+const CanvasUtil = {
5 5
 
6 6
     /**
7 7
      * Draws a round rectangle with a glow. The glowWidth indicates the depth
@@ -15,8 +15,7 @@ var CanvasUtil = (function(my) {
15 15
      * @param glowColor the color of the glow
16 16
      * @param glowWidth the width of the glow
17 17
      */
18
-    my.drawRoundRectGlow
19
-        = function(drawContext, x, y, w, h, r, glowColor, glowWidth) {
18
+    drawRoundRectGlow (drawContext, x, y, w, h, r, glowColor, glowWidth) {
20 19
 
21 20
         // Save the previous state of the context.
22 21
         drawContext.save();
@@ -73,14 +72,14 @@ var CanvasUtil = (function(my) {
73 72
 
74 73
         // Restore the previous context state.
75 74
         drawContext.restore();
76
-    };
75
+    },
77 76
 
78 77
     /**
79 78
      * Clones the given canvas.
80 79
      *
81 80
      * @return the new cloned canvas.
82 81
      */
83
-    my.cloneCanvas = function (oldCanvas) {
82
+    cloneCanvas (oldCanvas) {
84 83
         /*
85 84
          * FIXME Testing has shown that oldCanvas may not exist. In such a case,
86 85
          * the method CanvasUtil.cloneCanvas may throw an error. Since audio
@@ -103,9 +102,7 @@ var CanvasUtil = (function(my) {
103 102
 
104 103
         //return the new canvas
105 104
         return newCanvas;
106
-    };
105
+    }
106
+};
107 107
 
108
-    return my;
109
-})(CanvasUtil || {});
110
-
111
-module.exports = CanvasUtil;
108
+export default CanvasUtil;

+ 145
- 0
modules/UI/authentication/AuthHandler.js 查看文件

@@ -0,0 +1,145 @@
1
+/* global JitsiMeetJS, APP */
2
+
3
+import LoginDialog from './LoginDialog';
4
+import UIEvents from '../../../service/UI/UIEvents';
5
+import UIUtil from '../util/UIUtil';
6
+import {openConnection} from '../../../connection';
7
+
8
+const ConferenceEvents = JitsiMeetJS.events.conference;
9
+
10
+let externalAuthWindow;
11
+let authRequiredDialog;
12
+
13
+/**
14
+ * Authenticate using external service or just focus
15
+ * external auth window if there is one already.
16
+ *
17
+ * @param {JitsiConference} room
18
+ * @param {string} [lockPassword] password to use if the conference is locked
19
+ */
20
+function doExternalAuth (room, lockPassword) {
21
+    if (externalAuthWindow) {
22
+        externalAuthWindow.focus();
23
+        return;
24
+    }
25
+    if (room.isJoined()) {
26
+        room.getExternalAuthUrl(true).then(function (url) {
27
+            externalAuthWindow = LoginDialog.showExternalAuthDialog(
28
+                url,
29
+                function () {
30
+                    externalAuthWindow = null;
31
+                    room.join(lockPassword);
32
+                }
33
+            );
34
+        });
35
+    } else {
36
+        // If conference has not been started yet
37
+        // then  redirect to login page
38
+        room.getExternalAuthUrl().then(UIUtil.redirect);
39
+    }
40
+}
41
+
42
+/**
43
+ * Authenticate on the server.
44
+ * @param {JitsiConference} room
45
+ * @param {string} [lockPassword] password to use if the conference is locked
46
+ */
47
+function doXmppAuth (room, lockPassword) {
48
+    let loginDialog = LoginDialog.showAuthDialog(function (id, password) {
49
+        // auth "on the fly":
50
+        // 1. open new connection with proper id and password
51
+        // 2. connect to the room
52
+        // (this will store sessionId in the localStorage)
53
+        // 3. close new connection
54
+        // 4. reallocate focus in current room
55
+        openConnection({id, password}).then(function (connection) {
56
+            // open room
57
+            let newRoom = connection.initJitsiConference(room.getName());
58
+
59
+            loginDialog.displayConnectionStatus(
60
+                APP.translation.translateString('connection.FETCH_SESSION_ID')
61
+            );
62
+
63
+            newRoom.room.moderator.authenticate().then(function () {
64
+                connection.disconnect();
65
+
66
+                loginDialog.displayConnectionStatus(
67
+                    APP.translation.translateString('connection.GOT_SESSION_ID')
68
+                );
69
+
70
+                if (room.isJoined()) {
71
+                    // just reallocate focus if already joined
72
+                    room.room.moderator.allocateConferenceFocus();
73
+                } else {
74
+                    // or join
75
+                    room.join(lockPassword);
76
+                }
77
+
78
+                loginDialog.close();
79
+            }).catch(function (error, code) {
80
+                connection.disconnect();
81
+
82
+                console.error('Auth on the fly failed', error);
83
+
84
+                let errorMsg = APP.translation.translateString(
85
+                    'connection.GET_SESSION_ID_ERROR'
86
+                );
87
+
88
+                loginDialog.displayError(errorMsg + code);
89
+            });
90
+        }, function (err) {
91
+            loginDialog.displayError(err);
92
+        });
93
+    }, function () { // user canceled
94
+        loginDialog.close();
95
+    });
96
+}
97
+
98
+/**
99
+ * Authenticate for the conference.
100
+ * Uses external service for auth if conference supports that.
101
+ * @param {JitsiConference} room
102
+ * @param {string} [lockPassword] password to use if the conference is locked
103
+ */
104
+function authenticate (room, lockPassword) {
105
+    if (room.isExternalAuthEnabled()) {
106
+        doExternalAuth(room, lockPassword);
107
+    } else {
108
+        doXmppAuth();
109
+    }
110
+}
111
+
112
+/**
113
+ * Notify user that authentication is required to create the conference.
114
+ */
115
+function requireAuth(roomName) {
116
+    if (authRequiredDialog) {
117
+        return;
118
+    }
119
+
120
+    authRequiredDialog = LoginDialog.showAuthRequiredDialog(
121
+        roomName, authenticate
122
+    );
123
+}
124
+
125
+/**
126
+ * Close auth-related dialogs if there are any.
127
+ */
128
+function closeAuth() {
129
+    if (externalAuthWindow) {
130
+        externalAuthWindow.close();
131
+        externalAuthWindow = null;
132
+    }
133
+
134
+    if (authRequiredDialog) {
135
+        authRequiredDialog.close();
136
+        authRequiredDialog = null;
137
+    }
138
+}
139
+
140
+
141
+export default {
142
+    authenticate,
143
+    requireAuth,
144
+    closeAuth
145
+};

+ 0
- 124
modules/UI/authentication/Authentication.js 查看文件

@@ -1,124 +0,0 @@
1
-/* global $, APP*/
2
-
3
-var LoginDialog = require('./LoginDialog');
4
-var Moderator = require('../../xmpp/moderator');
5
-
6
-/* Initial "authentication required" dialog */
7
-var authDialog = null;
8
-/* Loop retry ID that wits for other user to create the room */
9
-var authRetryId = null;
10
-var authenticationWindow = null;
11
-
12
-var Authentication = {
13
-    openAuthenticationDialog: function (roomName, intervalCallback, callback) {
14
-        // This is the loop that will wait for the room to be created by
15
-        // someone else. 'auth_required.moderator' will bring us back here.
16
-        authRetryId = window.setTimeout(intervalCallback, 5000);
17
-        // Show prompt only if it's not open
18
-        if (authDialog !== null) {
19
-            return;
20
-        }
21
-        // extract room name from 'room@muc.server.net'
22
-        var room = roomName.substr(0, roomName.indexOf('@'));
23
-
24
-        var title
25
-            = APP.translation.generateTranslationHTML("dialog.WaitingForHost");
26
-        var msg
27
-            = APP.translation.generateTranslationHTML(
28
-                    "dialog.WaitForHostMsg", {room: room});
29
-
30
-        var buttonTxt
31
-            = APP.translation.generateTranslationHTML("dialog.IamHost");
32
-        var buttons = [];
33
-        buttons.push({title: buttonTxt, value: "authNow"});
34
-
35
-        authDialog = APP.UI.messageHandler.openDialog(
36
-            title,
37
-            msg,
38
-            true,
39
-            buttons,
40
-            function (onSubmitEvent, submitValue) {
41
-
42
-                // Do not close the dialog yet
43
-                onSubmitEvent.preventDefault();
44
-
45
-                // Open login popup
46
-                if (submitValue === 'authNow') {
47
-                    callback();
48
-                }
49
-            }
50
-        );
51
-    },
52
-    closeAuthenticationWindow: function () {
53
-        if (authenticationWindow) {
54
-            authenticationWindow.close();
55
-            authenticationWindow = null;
56
-        }
57
-    },
58
-    xmppAuthenticate: function () {
59
-
60
-        var loginDialog = LoginDialog.show(
61
-            function (connection, state) {
62
-                if (!state) {
63
-                    // User cancelled
64
-                    loginDialog.close();
65
-                    return;
66
-                } else if (state == APP.xmpp.Status.CONNECTED) {
67
-
68
-                    loginDialog.close();
69
-
70
-                    Authentication.stopInterval();
71
-                    Authentication.closeAuthenticationDialog();
72
-
73
-                    // Close the connection as anonymous one will be used
74
-                    // to create the conference. Session-id will authorize
75
-                    // the request.
76
-                    connection.disconnect();
77
-
78
-                    var roomName = APP.UI.generateRoomName();
79
-                    Moderator.allocateConferenceFocus(roomName, function () {
80
-                        // If it's not "on the fly" authentication now join
81
-                        // the conference room
82
-                        if (!APP.xmpp.isMUCJoined()) {
83
-                            APP.UI.checkForNicknameAndJoin();
84
-                        }
85
-                    });
86
-                }
87
-            }, true);
88
-    },
89
-    focusAuthenticationWindow: function () {
90
-        // If auth window exists just bring it to the front
91
-        if (authenticationWindow) {
92
-            authenticationWindow.focus();
93
-            return;
94
-        }
95
-    },
96
-    closeAuthenticationDialog: function () {
97
-        // Close authentication dialog if opened
98
-        if (authDialog) {
99
-            authDialog.close();
100
-            authDialog = null;
101
-        }
102
-    },
103
-    createAuthenticationWindow: function (callback, url) {
104
-        authenticationWindow = APP.UI.messageHandler.openCenteredPopup(
105
-            url, 910, 660,
106
-            // On closed
107
-            function () {
108
-                // Close authentication dialog if opened
109
-                Authentication.closeAuthenticationDialog();
110
-                callback();
111
-                authenticationWindow = null;
112
-            });
113
-        return authenticationWindow;
114
-    },
115
-    stopInterval: function () {
116
-        // Clear retry interval, so that we don't call 'doJoinAfterFocus' twice
117
-        if (authRetryId) {
118
-            window.clearTimeout(authRetryId);
119
-            authRetryId = null;
120
-        }
121
-    }
122
-};
123
-
124
-module.exports = Authentication;

+ 166
- 170
modules/UI/authentication/LoginDialog.js 查看文件

@@ -1,75 +1,102 @@
1 1
 /* global $, APP, config*/
2 2
 
3
-var XMPP = require('../../xmpp/xmpp');
4
-var Moderator = require('../../xmpp/moderator');
5
-
6
-//FIXME: use LoginDialog to add retries to XMPP.connect method used when
7
-// anonymous domain is not enabled
3
+var messageHandler = require('../util/MessageHandler');
8 4
 
9 5
 /**
10
- * Creates new <tt>Dialog</tt> instance.
11
- * @param callback <tt>function(Strophe.Connection, Strophe.Status)</tt> called
12
- *        when we either fail to connect or succeed(check Strophe.Status).
13
- * @param obtainSession <tt>true</tt> if we want to send ConferenceIQ to Jicofo
14
- *        in order to create session-id after the connection is established.
15
- * @constructor
6
+ * Build html for "password required" dialog.
7
+ * @returns {string} html string
16 8
  */
17
-function Dialog(callback, obtainSession) {
18
-
19
-    var self = this;
20
-
21
-    var stop = false;
9
+function getPasswordInputHtml() {
10
+    let placeholder = config.hosts.authdomain
11
+        ? "user identity"
12
+        : "user@domain.net";
13
+    let passRequiredMsg = APP.translation.translateString(
14
+        "dialog.passwordRequired"
15
+    );
16
+    return `
17
+        <h2 data-i18n="dialog.passwordRequired">${passRequiredMsg}</h2>
18
+        <input name="username" type="text" placeholder=${placeholder} autofocus>
19
+        <input name="password" type="password"
20
+               data-i18n="[placeholder]dialog.userPassword"
21
+               placeholder="user password">
22
+        `;
23
+}
22 24
 
23
-    var connection = APP.xmpp.createConnection();
25
+/**
26
+ * Convert provided id to jid if it's not jid yet.
27
+ * @param {string} id user id or jid
28
+ * @returns {string} jid
29
+ */
30
+function toJid(id) {
31
+    if (id.indexOf("@") >= 0) {
32
+        return id;
33
+    }
24 34
 
25
-    var message = '<h2 data-i18n="dialog.passwordRequired">';
26
-    message += APP.translation.translateString("dialog.passwordRequired");
27
-    message += '</h2>' +
28
-        '<input name="username" type="text" ';
35
+    let jid = id.concat('@');
29 36
     if (config.hosts.authdomain) {
30
-      message += 'placeholder="user identity" autofocus>';
37
+        jid += config.hosts.authdomain;
31 38
     } else {
32
-      message += 'placeholder="user@domain.net" autofocus>';
39
+        jid += config.hosts.domain;
33 40
     }
34
-    message += '<input name="password" ' +
35
-        'type="password" data-i18n="[placeholder]dialog.userPassword"' +
36
-        ' placeholder="user password">';
37 41
 
38
-    var okButton = APP.translation.generateTranslationHTML("dialog.Ok");
42
+    return jid;
43
+}
39 44
 
40
-    var cancelButton = APP.translation.generateTranslationHTML("dialog.Cancel");
45
+/**
46
+ * Generate cancel button config for the dialog.
47
+ * @returns {Object}
48
+ */
49
+function cancelButton() {
50
+    return {
51
+        title: APP.translation.generateTranslationHTML("dialog.Cancel"),
52
+        value: false
53
+    };
54
+}
41 55
 
42
-    var states = {
56
+/**
57
+ * Auth dialog for JitsiConnection which supports retries.
58
+ * If no cancelCallback provided then there will be
59
+ * no cancel button on the dialog.
60
+ *
61
+ * @class LoginDialog
62
+ * @constructor
63
+ *
64
+ * @param {function(jid, password)} successCallback
65
+ * @param {function} [cancelCallback] callback to invoke if user canceled.
66
+ */
67
+function LoginDialog(successCallback, cancelCallback) {
68
+    let loginButtons = [{
69
+        title: APP.translation.generateTranslationHTML("dialog.Ok"),
70
+        value: true
71
+    }];
72
+    let finishedButtons = [{
73
+        title: APP.translation.translateString('dialog.retry'),
74
+        value: 'retry'
75
+    }];
76
+
77
+    // show "cancel" button only if cancelCallback provided
78
+    if (cancelCallback) {
79
+        loginButtons.push(cancelButton());
80
+        finishedButtons.push(cancelButton());
81
+    }
82
+
83
+    const states = {
43 84
         login: {
44
-            html: message,
45
-            buttons: [
46
-                { title: okButton, value: true},
47
-                { title: cancelButton, value: false}
48
-            ],
85
+            html: getPasswordInputHtml(),
86
+            buttons: loginButtons,
49 87
             focus: ':input:first',
50 88
             submit: function (e, v, m, f) {
51 89
                 e.preventDefault();
52 90
                 if (v) {
53
-                    var jid = f.username;
54
-                    var password = f.password;
91
+                    let jid = f.username;
92
+                    let password = f.password;
55 93
                     if (jid && password) {
56
-                        stop = false;
57
-                        if (jid.indexOf("@") < 0) {
58
-                          jid = jid.concat('@');
59
-                          if (config.hosts.authdomain) {
60
-                            jid += config.hosts.authdomain;
61
-                          } else {
62
-                            jid += config.hosts.domain;
63
-                          }
64
-                        }
65
-                        connection.reset();
66 94
                         connDialog.goToState('connecting');
67
-                        connection.connect(jid, password, stateHandler);
95
+                        successCallback(toJid(jid), password);
68 96
                     }
69 97
                 } else {
70 98
                     // User cancelled
71
-                    stop = true;
72
-                    callback();
99
+                    cancelCallback();
73 100
                 }
74 101
             }
75 102
         },
@@ -82,115 +109,23 @@ function Dialog(callback, obtainSession) {
82 109
         finished: {
83 110
             title: APP.translation.translateString('dialog.error'),
84 111
             html:   '<div id="errorMessage"></div>',
85
-            buttons: [
86
-                {
87
-                    title: APP.translation.translateString('dialog.retry'),
88
-                    value: 'retry'
89
-                },
90
-                {
91
-                    title: APP.translation.translateString('dialog.Cancel'),
92
-                    value: 'cancel'
93
-                },
94
-            ],
112
+            buttons: finishedButtons,
95 113
             defaultButton: 0,
96 114
             submit: function (e, v, m, f) {
97 115
                 e.preventDefault();
98
-                if (v === 'retry')
116
+                if (v === 'retry') {
99 117
                     connDialog.goToState('login');
100
-                else
101
-                    callback();
118
+                } else {
119
+                    // User cancelled
120
+                    cancelCallback();
121
+                }
102 122
             }
103 123
         }
104 124
     };
105 125
 
106
-    var connDialog
107
-        = APP.UI.messageHandler.openDialogWithStates(states,
108
-                { persistent: true, closeText: '' }, null);
109
-
110
-    var stateHandler = function (status, message) {
111
-        if (stop) {
112
-            return;
113
-        }
114
-
115
-        var translateKey = "connection." + XMPP.getStatusString(status);
116
-        var statusStr = APP.translation.translateString(translateKey);
117
-
118
-        // Display current state
119
-        var connectionStatus =
120
-            connDialog.getState('connecting').find('#connectionStatus');
121
-
122
-        connectionStatus.text(statusStr);
123
-
124
-        switch (status) {
125
-            case XMPP.Status.CONNECTED:
126
-
127
-                stop = true;
128
-                if (!obtainSession) {
129
-                    callback(connection, status);
130
-                    return;
131
-                }
132
-                // Obtaining session-id status
133
-                connectionStatus.text(
134
-                    APP.translation.translateString(
135
-                        'connection.FETCH_SESSION_ID'));
136
-
137
-                // Authenticate with Jicofo and obtain session-id
138
-                var roomName = APP.UI.generateRoomName();
139
-
140
-                // Jicofo will return new session-id when connected
141
-                // from authenticated domain
142
-                connection.sendIQ(
143
-                    Moderator.createConferenceIq(roomName),
144
-                    function (result) {
145
-
146
-                        connectionStatus.text(
147
-                            APP.translation.translateString(
148
-                                'connection.GOT_SESSION_ID'));
149
-
150
-                        stop = true;
151
-
152
-                        // Parse session-id
153
-                        Moderator.parseSessionId(result);
154
-
155
-                        callback(connection, status);
156
-                    },
157
-                    function (error) {
158
-                        console.error("Auth on the fly failed", error);
159
-
160
-                        stop = true;
161
-
162
-                        var errorMsg =
163
-                            APP.translation.translateString(
164
-                                'connection.GET_SESSION_ID_ERROR') +
165
-                                $(error).find('>error').attr('code');
166
-
167
-                        self.displayError(errorMsg);
168
-
169
-                        connection.disconnect();
170
-                    });
171
-
172
-                break;
173
-            case XMPP.Status.AUTHFAIL:
174
-            case XMPP.Status.CONNFAIL:
175
-            case XMPP.Status.DISCONNECTED:
176
-
177
-                stop = true;
178
-
179
-                callback(connection, status);
180
-
181
-                var errorMessage = statusStr;
182
-
183
-                if (message)
184
-                {
185
-                    errorMessage += ': ' + message;
186
-                }
187
-                self.displayError(errorMessage);
188
-
189
-                break;
190
-            default:
191
-                break;
192
-        }
193
-    };
126
+    var connDialog = messageHandler.openDialogWithStates(
127
+        states, { persistent: true, closeText: '' }, null
128
+    );
194 129
 
195 130
     /**
196 131
      * Displays error message in 'finished' state which allows either to cancel
@@ -199,42 +134,103 @@ function Dialog(callback, obtainSession) {
199 134
      */
200 135
     this.displayError = function (message) {
201 136
 
202
-        var finishedState = connDialog.getState('finished');
137
+        let finishedState = connDialog.getState('finished');
203 138
 
204
-        var errorMessageElem = finishedState.find('#errorMessage');
139
+        let errorMessageElem = finishedState.find('#errorMessage');
205 140
         errorMessageElem.text(message);
206 141
 
207 142
         connDialog.goToState('finished');
208 143
     };
209 144
 
145
+    /**
146
+     *  Show message as connection status.
147
+     * @param {string} message
148
+     */
149
+    this.displayConnectionStatus = function (message) {
150
+        let connectingState = connDialog.getState('connecting');
151
+
152
+        let connectionStatus = connectingState.find('#connectionStatus');
153
+        connectionStatus.text(message);
154
+    };
155
+
210 156
     /**
211 157
      * Closes LoginDialog.
212 158
      */
213 159
     this.close = function () {
214
-        stop = true;
215 160
         connDialog.close();
216 161
     };
217 162
 }
218 163
 
219
-var LoginDialog = {
164
+export default {
165
+
166
+    /**
167
+     * Show new auth dialog for JitsiConnection.
168
+     *
169
+     * @param {function(jid, password)} successCallback
170
+     * @param {function} [cancelCallback] callback to invoke if user canceled.
171
+     *
172
+     * @returns {LoginDialog}
173
+     */
174
+    showAuthDialog: function (successCallback, cancelCallback) {
175
+        return new LoginDialog(successCallback, cancelCallback);
176
+    },
177
+
178
+    /**
179
+     * Show notification that external auth is required (using provided url).
180
+     * @param {string} url URL to use for external auth.
181
+     * @param {function} callback callback to invoke when auth popup is closed.
182
+     * @returns auth dialog
183
+     */
184
+    showExternalAuthDialog: function (url, callback) {
185
+        var dialog = messageHandler.openCenteredPopup(
186
+            url, 910, 660,
187
+            // On closed
188
+            callback
189
+        );
190
+
191
+        if (!dialog) {
192
+            messageHandler.openMessageDialog(null, "dialog.popupError");
193
+        }
194
+
195
+        return dialog;
196
+    },
220 197
 
221 198
     /**
222
-     * Displays login prompt used to establish new XMPP connection. Given
223
-     * <tt>callback(Strophe.Connection, Strophe.Status)</tt> function will be
224
-     * called when we connect successfully(status === CONNECTED) or when we fail
225
-     * to do so. On connection failure program can call Dialog.close() method in
226
-     * order to cancel or do nothing to let the user retry.
227
-     * @param callback <tt>function(Strophe.Connection, Strophe.Status)</tt>
228
-     *        called when we either fail to connect or succeed(check
229
-     *        Strophe.Status).
230
-     * @param obtainSession <tt>true</tt> if we want to send ConferenceIQ to
231
-     *        Jicofo in order to create session-id after the connection is
232
-     *        established.
233
-     * @returns {Dialog}
199
+     * Show notification that authentication is required
200
+     * to create the conference, so he should authenticate or wait for a host.
201
+     * @param {string} roomName name of the conference
202
+     * @param {function} onAuthNow callback to invoke if
203
+     * user want to authenticate.
204
+     * @returns dialog
234 205
      */
235
-    show: function (callback, obtainSession) {
236
-        return new Dialog(callback, obtainSession);
206
+    showAuthRequiredDialog: function (roomName, onAuthNow) {
207
+        var title = APP.translation.generateTranslationHTML(
208
+            "dialog.WaitingForHost"
209
+        );
210
+        var msg = APP.translation.generateTranslationHTML(
211
+            "dialog.WaitForHostMsg", {room: roomName}
212
+        );
213
+
214
+        var buttonTxt = APP.translation.generateTranslationHTML(
215
+            "dialog.IamHost"
216
+        );
217
+        var buttons = [{title: buttonTxt, value: "authNow"}];
218
+
219
+        return APP.UI.messageHandler.openDialog(
220
+            title,
221
+            msg,
222
+            true,
223
+            buttons,
224
+            function (e, submitValue) {
225
+
226
+                // Do not close the dialog yet
227
+                e.preventDefault();
228
+
229
+                // Open login popup
230
+                if (submitValue === 'authNow') {
231
+                    onAuthNow();
232
+                }
233
+            }
234
+        );
237 235
     }
238 236
 };
239
-
240
-module.exports = LoginDialog;

+ 189
- 0
modules/UI/authentication/RoomLocker.js 查看文件

@@ -0,0 +1,189 @@
1
+/* global APP, JitsiMeetJS */
2
+import messageHandler from '../util/MessageHandler';
3
+import UIUtil from '../util/UIUtil';
4
+//FIXME:
5
+import AnalyticsAdapter from '../../statistics/AnalyticsAdapter';
6
+
7
+/**
8
+ * Show dialog which asks user for new password for the conference.
9
+ * @returns {Promise<string>} password or nothing if user canceled
10
+ */
11
+function askForNewPassword () {
12
+    let passMsg = APP.translation.generateTranslationHTML("dialog.passwordMsg");
13
+    let yourPassMsg = APP.translation.translateString("dialog.yourPassword");
14
+    let msg = `
15
+        <h2>${passMsg}</h2>
16
+        <input name="lockKey" type="text"
17
+               data-i18n="[placeholder]dialog.yourPassword"
18
+               placeholder="${yourPassMsg}" autofocus>
19
+    `;
20
+
21
+    return new Promise(function (resolve, reject) {
22
+        messageHandler.openTwoButtonDialog(
23
+            null, null, null,
24
+            msg, false, "dialog.Save",
25
+            function (e, v, m, f) {
26
+                if (v && f.lockKey) {
27
+                    resolve(UIUtil.escapeHtml(f.lockKey));
28
+                } else {
29
+                    reject();
30
+                }
31
+            },
32
+            null, null, 'input:first'
33
+        );
34
+    });
35
+}
36
+
37
+/**
38
+ * Show dialog which asks for required conference password.
39
+ * @returns {Promise<string>} password or nothing if user canceled
40
+ */
41
+function askForPassword () {
42
+    let passRequiredMsg = APP.translation.translateString(
43
+        "dialog.passwordRequired"
44
+    );
45
+    let passMsg = APP.translation.translateString("dialog.password");
46
+    let msg = `
47
+        <h2 data-i18n="dialog.passwordRequired">${passRequiredMsg}</h2>
48
+        <input name="lockKey" type="text"
49
+               data-i18n="[placeholder]dialog.password"
50
+               placeholder="${passMsg}" autofocus>
51
+    `;
52
+    return new Promise(function (resolve, reject) {
53
+        messageHandler.openTwoButtonDialog(
54
+            null, null, null, msg,
55
+            true, "dialog.Ok",
56
+            function (e, v, m, f) {}, null,
57
+            function (e, v, m, f) {
58
+                if (v && f.lockKey) {
59
+                    resolve(UIUtil.escapeHtml(f.lockKey));
60
+                } else {
61
+                    reject();
62
+                }
63
+            },
64
+            ':input:first'
65
+        );
66
+    });
67
+}
68
+
69
+/**
70
+ * Show dialog which asks if user want remove password from the conference.
71
+ * @returns {Promise}
72
+ */
73
+function askToUnlock () {
74
+    return new Promise(function (resolve, reject) {
75
+        messageHandler.openTwoButtonDialog(
76
+            null, null, "dialog.passwordCheck",
77
+            null, false, "dialog.Remove",
78
+            function (e, v) {
79
+                if (v) {
80
+                    resolve();
81
+                } else {
82
+                    reject();
83
+                }
84
+            }
85
+        );
86
+    });
87
+}
88
+
89
+/**
90
+ * Show notification that user cannot set password for the conference
91
+ * because server doesn't support that.
92
+ */
93
+function notifyPasswordNotSupported () {
94
+    console.warn('room passwords not supported');
95
+    messageHandler.showError("dialog.warning", "dialog.passwordNotSupported");
96
+}
97
+
98
+/**
99
+ * Show notification that setting password for the conference failed.
100
+ * @param {Error} err error
101
+ */
102
+function notifyPasswordFailed(err) {
103
+    console.warn('setting password failed', err);
104
+    messageHandler.showError("dialog.lockTitle", "dialog.lockMessage");
105
+}
106
+
107
+const ConferenceErrors = JitsiMeetJS.errors.conference;
108
+
109
+/**
110
+ * Create new RoomLocker for the conference.
111
+ * It allows to set or remove password for the conference,
112
+ * or ask for required password.
113
+ * @returns {RoomLocker}
114
+ */
115
+export default function createRoomLocker (room) {
116
+    let password;
117
+
118
+    function lock (newPass) {
119
+        return room.lock(newPass).then(function () {
120
+            password = newPass;
121
+        }).catch(function (err) {
122
+            console.error(err);
123
+            if (err === ConferenceErrors.PASSWORD_NOT_SUPPORTED) {
124
+                notifyPasswordNotSupported();
125
+            } else {
126
+                notifyPasswordFailed(err);
127
+            }
128
+            throw err;
129
+        });
130
+    }
131
+
132
+    /**
133
+     * @class RoomLocker
134
+     */
135
+    return {
136
+        get isLocked () {
137
+            return !!password;
138
+        },
139
+
140
+        get password () {
141
+            return password;
142
+        },
143
+
144
+        /**
145
+         * Allows to remove password from the conference (asks user first).
146
+         * @returns {Promise}
147
+         */
148
+        askToUnlock () {
149
+            return askToUnlock().then(function () {
150
+                return lock();
151
+            }).then(function () {
152
+                AnalyticsAdapter.sendEvent('toolbar.lock.disabled');
153
+            });
154
+        },
155
+
156
+        /**
157
+         * Allows to set password for the conference.
158
+         * It asks user for new password and locks the room.
159
+         * @returns {Promise}
160
+         */
161
+        askToLock () {
162
+            return askForNewPassword().then(function (newPass) {
163
+                return lock(newPass);
164
+            }).then(function () {
165
+                AnalyticsAdapter.sendEvent('toolbar.lock.enabled');
166
+            });
167
+        },
168
+
169
+        /**
170
+         * Asks user for required conference password.
171
+         */
172
+        requirePassword () {
173
+            return askForPassword().then(function (newPass) {
174
+                password = newPass;
175
+            });
176
+        },
177
+
178
+        /**
179
+         * Show notification that to set/remove password user must be moderator.
180
+         */
181
+        notifyModeratorRequired () {
182
+            if (password) {
183
+                messageHandler.openMessageDialog(null, "dialog.passwordError");
184
+            } else {
185
+                messageHandler.openMessageDialog(null, "dialog.passwordError2");
186
+            }
187
+        }
188
+    };
189
+}

+ 38
- 44
modules/UI/avatar/Avatar.js 查看文件

@@ -1,68 +1,62 @@
1
-/* global Strophe, APP, MD5, config, interfaceConfig */
2
-var Settings = require("../../settings/Settings");
1
+/* global MD5, config, interfaceConfig */
3 2
 
4
-var users = {};
3
+let users = {};
5 4
 
6
-var Avatar = {
5
+export default {
7 6
 
8 7
     /**
9 8
      * Sets the user's avatar in the settings menu(if local user), contact list
10 9
      * and thumbnail
11
-     * @param jid jid of the user
12
-     * @param id email or userID to be used as a hash
10
+     * @param id id of the user
11
+     * @param email email or nickname to be used as a hash
13 12
      */
14
-    setUserAvatar: function (jid, id) {
15
-        if (id) {
16
-            if (users[jid] === id) {
13
+    setUserAvatar: function (id, email) {
14
+        if (email) {
15
+            if (users[id] === email) {
17 16
                 return;
18 17
             }
19
-            users[jid] = id;
18
+            users[id] = email;
20 19
         }
21
-        var avatarUrl = this.getAvatarUrl(jid);
22
-        var resourceJid = Strophe.getResourceFromJid(jid);
23
-
24
-        APP.UI.userAvatarChanged(resourceJid, avatarUrl);
25 20
     },
21
+
26 22
     /**
27 23
      * Returns the URL of the image for the avatar of a particular user,
28
-     * identified by its jid
29
-     * @param jid
30
-     * @param jid full MUC jid of the user for whom we want to obtain avatar URL
24
+     * identified by its id.
25
+     * @param {string} userId user id
31 26
      */
32
-    getAvatarUrl: function (jid) {
27
+    getAvatarUrl: function (userId) {
33 28
         if (config.disableThirdPartyRequests) {
34 29
             return 'images/avatar2.png';
35
-        } else {
36
-            if (!jid) {
37
-                console.error("Get avatar - jid is undefined");
38
-                return null;
39
-            }
40
-            var id = users[jid];
30
+        }
41 31
 
42
-            // If the ID looks like an email, we'll use gravatar.
43
-            // Otherwise, it's a random avatar, and we'll use the configured
44
-            // URL.
45
-            var random = !id || id.indexOf('@') < 0;
32
+        if (!userId) {
33
+            console.error("Get avatar - id is undefined");
34
+            return null;
35
+        }
46 36
 
47
-            if (!id) {
48
-                console.warn(
49
-                    "No avatar stored yet for " + jid + " - using JID as ID");
50
-                id = jid;
51
-            }
52
-            id = MD5.hexdigest(id.trim().toLowerCase());
37
+        let avatarId = users[userId];
53 38
 
54
-            // Default to using gravatar.
55
-            var urlPref = 'https://www.gravatar.com/avatar/';
56
-            var urlSuf = "?d=wavatar&size=100";
39
+        // If the ID looks like an email, we'll use gravatar.
40
+        // Otherwise, it's a random avatar, and we'll use the configured
41
+        // URL.
42
+        let random = !avatarId || avatarId.indexOf('@') < 0;
57 43
 
58
-            if (random && interfaceConfig.RANDOM_AVATAR_URL_PREFIX) {
59
-                urlPref = interfaceConfig.RANDOM_AVATAR_URL_PREFIX;
60
-                urlSuf = interfaceConfig.RANDOM_AVATAR_URL_SUFFIX;
61
-            }
44
+        if (!avatarId) {
45
+            console.warn(
46
+                `No avatar stored yet for ${userId} - using ID as avatar ID`);
47
+            avatarId = userId;
48
+        }
49
+        avatarId = MD5.hexdigest(avatarId.trim().toLowerCase());
50
+
51
+        // Default to using gravatar.
52
+        let urlPref = 'https://www.gravatar.com/avatar/';
53
+        let urlSuf = "?d=wavatar&size=100";
62 54
 
63
-            return urlPref + id + urlSuf;
55
+        if (random && interfaceConfig.RANDOM_AVATAR_URL_PREFIX) {
56
+            urlPref = interfaceConfig.RANDOM_AVATAR_URL_PREFIX;
57
+            urlSuf = interfaceConfig.RANDOM_AVATAR_URL_SUFFIX;
64 58
         }
59
+
60
+        return urlPref + avatarId + urlSuf;
65 61
     }
66 62
 };
67
-
68
-module.exports = Avatar;

+ 138
- 87
modules/UI/etherpad/Etherpad.js 查看文件

@@ -1,61 +1,20 @@
1
-/* global $, config,
2
-   setLargeVideoVisible, Util */
1
+/* global $ */
3 2
 
4
-var VideoLayout = require("../videolayout/VideoLayout");
5
-var Prezi = require("../prezi/Prezi");
6
-var UIUtil = require("../util/UIUtil");
7
-
8
-var etherpadName = null;
9
-var etherpadIFrame = null;
10
-var domain = null;
11
-var options = "?showControls=true&showChat=false&showLineNumbers=true" +
12
-    "&useMonospaceFont=false";
13
-
14
-
15
-/**
16
- * Resizes the etherpad.
17
- */
18
-function resize() {
19
-    if ($('#etherpad>iframe').length) {
20
-        var remoteVideos = $('#remoteVideos');
21
-        var availableHeight
22
-            = window.innerHeight - remoteVideos.outerHeight();
23
-        var availableWidth = UIUtil.getAvailableVideoWidth();
24
-
25
-        $('#etherpad>iframe').width(availableWidth);
26
-        $('#etherpad>iframe').height(availableHeight);
27
-    }
28
-}
3
+import VideoLayout from "../videolayout/VideoLayout";
4
+import LargeContainer from '../videolayout/LargeContainer';
5
+import UIUtil from "../util/UIUtil";
6
+import SidePanelToggler from "../side_pannels/SidePanelToggler";
7
+import BottomToolbar from '../toolbars/BottomToolbar';
29 8
 
30 9
 /**
31
- * Creates the Etherpad button and adds it to the toolbar.
10
+ * Etherpad options.
32 11
  */
33
-function enableEtherpadButton() {
34
-    if (!$('#toolbar_button_etherpad').is(":visible"))
35
-        $('#toolbar_button_etherpad').css({display: 'inline-block'});
36
-}
37
-
38
-/**
39
- * Creates the IFrame for the etherpad.
40
- */
41
-function createIFrame() {
42
-    etherpadIFrame = VideoLayout.createEtherpadIframe(
43
-            domain + etherpadName + options, function() {
44
-
45
-                document.domain = document.domain;
46
-                bubbleIframeMouseMove(etherpadIFrame);
47
-                setTimeout(function() {
48
-                    // the iframes inside of the etherpad are
49
-                    // not yet loaded when the etherpad iframe is loaded
50
-                    var outer = etherpadIFrame.
51
-                        contentDocument.getElementsByName("ace_outer")[0];
52
-                    bubbleIframeMouseMove(outer);
53
-                    var inner = outer.
54
-                        contentDocument.getElementsByName("ace_inner")[0];
55
-                    bubbleIframeMouseMove(inner);
56
-                }, 2000);
57
-            });
58
-}
12
+const options = $.param({
13
+    showControns: true,
14
+    showChat: false,
15
+    showLineNumbers: true,
16
+    useMonospaceFont: false
17
+});
59 18
 
60 19
 function bubbleIframeMouseMove(iframe){
61 20
     var existingOnMouseMove = iframe.contentWindow.onmousemove;
@@ -71,8 +30,8 @@ function bubbleIframeMouseMove(iframe){
71 30
             e.detail,
72 31
             e.screenX,
73 32
             e.screenY,
74
-                e.clientX + boundingClientRect.left,
75
-                e.clientY + boundingClientRect.top,
33
+            e.clientX + boundingClientRect.left,
34
+            e.clientY + boundingClientRect.top,
76 35
             e.ctrlKey,
77 36
             e.altKey,
78 37
             e.shiftKey,
@@ -84,48 +43,140 @@ function bubbleIframeMouseMove(iframe){
84 43
     };
85 44
 }
86 45
 
46
+/**
47
+ * Default Etherpad frame width.
48
+ */
49
+const DEFAULT_WIDTH = 640;
50
+/**
51
+ * Default Etherpad frame height.
52
+ */
53
+const DEFAULT_HEIGHT = 480;
54
+
55
+const EtherpadContainerType = "etherpad";
87 56
 
88
-var Etherpad = {
89
-    /**
90
-     * Initializes the etherpad.
91
-     */
92
-    init: function (name) {
57
+/**
58
+ * Container for Etherpad iframe.
59
+ */
60
+class Etherpad extends LargeContainer {
61
+    constructor (domain, name) {
62
+        super();
63
+
64
+        const iframe = document.createElement('iframe');
93 65
 
94
-        if (config.etherpad_base && !etherpadName && name) {
66
+        iframe.src = domain + name + '?' + options;
67
+        iframe.frameBorder = 0;
68
+        iframe.scrolling = "no";
69
+        iframe.width = DEFAULT_WIDTH;
70
+        iframe.height = DEFAULT_HEIGHT;
71
+        iframe.setAttribute('style', 'visibility: hidden;');
95 72
 
96
-            domain = config.etherpad_base;
73
+        this.container.appendChild(iframe);
97 74
 
98
-            etherpadName = name;
75
+        iframe.onload = function() {
76
+            document.domain = document.domain;
77
+            bubbleIframeMouseMove(iframe);
99 78
 
100
-            enableEtherpadButton();
79
+            setTimeout(function() {
80
+                const doc = iframe.contentDocument;
101 81
 
102
-            /**
103
-             * Resizes the etherpad, when the window is resized.
104
-             */
105
-            $(window).resize(function () {
106
-                resize();
82
+                // the iframes inside of the etherpad are
83
+                // not yet loaded when the etherpad iframe is loaded
84
+                const outer = doc.getElementsByName("ace_outer")[0];
85
+                bubbleIframeMouseMove(outer);
86
+
87
+                const inner = doc.getElementsByName("ace_inner")[0];
88
+                bubbleIframeMouseMove(inner);
89
+            }, 2000);
90
+        };
91
+
92
+        this.iframe = iframe;
93
+    }
94
+
95
+    get isOpen () {
96
+        return !!this.iframe;
97
+    }
98
+
99
+    get container () {
100
+        return document.getElementById('etherpad');
101
+    }
102
+
103
+    resize (containerWidth, containerHeight, animate) {
104
+        let height = containerHeight - BottomToolbar.getFilmStripHeight();
105
+        let width = containerWidth;
106
+
107
+        $(this.iframe).width(width).height(height);
108
+    }
109
+
110
+    show () {
111
+        const $iframe = $(this.iframe);
112
+        const $container = $(this.container);
113
+
114
+        return new Promise(resolve => {
115
+            $iframe.fadeIn(300, function () {
116
+                document.body.style.background = '#eeeeee';
117
+                $iframe.css({visibility: 'visible'});
118
+                $container.css({zIndex: 2});
119
+                resolve();
107 120
             });
121
+        });
122
+    }
123
+
124
+    hide () {
125
+        const $iframe = $(this.iframe);
126
+        const $container = $(this.container);
127
+
128
+        return new Promise(resolve => {
129
+            $iframe.fadeOut(300, function () {
130
+                $iframe.css({visibility: 'hidden'});
131
+                $container.css({zIndex: 0});
132
+                resolve();
133
+            });
134
+        });
135
+    }
136
+}
137
+
138
+/**
139
+ * Manager of the Etherpad frame.
140
+ */
141
+export default class EtherpadManager {
142
+    constructor (domain, name) {
143
+        if (!domain || !name) {
144
+            throw new Error("missing domain or name");
108 145
         }
109
-    },
146
+
147
+        this.domain = domain;
148
+        this.name = name;
149
+        this.etherpad = null;
150
+    }
151
+
152
+    get isOpen () {
153
+        return !!this.etherpad;
154
+    }
110 155
 
111 156
     /**
112
-     * Opens/hides the Etherpad.
157
+     * Create new Etherpad frame.
113 158
      */
114
-    toggleEtherpad: function (isPresentation) {
115
-        if (!etherpadIFrame)
116
-            createIFrame();
117
-
159
+    openEtherpad () {
160
+        this.etherpad = new Etherpad(this.domain, this.name);
161
+        VideoLayout.addLargeVideoContainer(
162
+            EtherpadContainerType,
163
+            this.etherpad
164
+        );
165
+    }
118 166
 
119
-        if(VideoLayout.getLargeVideoState() === "etherpad")
120
-        {
121
-            VideoLayout.setLargeVideoState("video");
122
-        }
123
-        else
124
-        {
125
-            VideoLayout.setLargeVideoState("etherpad");
167
+    /**
168
+     * Toggle Etherpad frame visibility.
169
+     * Open new Etherpad frame if there is no Etherpad frame yet.
170
+     */
171
+    toggleEtherpad () {
172
+        if (!this.isOpen) {
173
+            this.openEtherpad();
126 174
         }
127
-        resize();
128
-    }
129
-};
130 175
 
131
-module.exports = Etherpad;
176
+        let isVisible = VideoLayout.isLargeContainerTypeVisible(
177
+            EtherpadContainerType
178
+        );
179
+
180
+        VideoLayout.showLargeVideoContainer(EtherpadContainerType, !isVisible);
181
+    }
182
+}

+ 390
- 285
modules/UI/prezi/Prezi.js 查看文件

@@ -1,343 +1,448 @@
1
-var ToolbarToggler = require("../toolbars/ToolbarToggler");
2
-var UIUtil = require("../util/UIUtil");
3
-var VideoLayout = require("../videolayout/VideoLayout");
4
-var messageHandler = require("../util/MessageHandler");
5
-var PreziPlayer = require("./PreziPlayer");
1
+/* global $, APP */
2
+/* jshint -W101 */
3
+
4
+import VideoLayout from "../videolayout/VideoLayout";
5
+import LargeContainer from '../videolayout/LargeContainer';
6
+import PreziPlayer from './PreziPlayer';
7
+import UIUtil from '../util/UIUtil';
8
+import UIEvents from '../../../service/UI/UIEvents';
9
+import messageHandler from '../util/MessageHandler';
10
+import ToolbarToggler from "../toolbars/ToolbarToggler";
11
+import SidePanelToggler from "../side_pannels/SidePanelToggler";
12
+import BottomToolbar from '../toolbars/BottomToolbar';
6 13
 
7
-var preziPlayer = null;
14
+/**
15
+ * Example of Prezi link.
16
+ */
17
+const defaultPreziLink = "http://prezi.com/wz7vhjycl7e6/my-prezi";
18
+const alphanumRegex = /^[a-z0-9-_\/&\?=;]+$/i;
19
+/**
20
+ * Default aspect ratio for Prezi frame.
21
+ */
22
+const aspectRatio = 16.0 / 9.0;
8 23
 
24
+/**
25
+ * Default Prezi frame width.
26
+ */
27
+const DEFAULT_WIDTH = 640;
28
+/**
29
+ * Default Prezi frame height.
30
+ */
31
+const DEFAULT_HEIGHT = 480;
9 32
 
10 33
 /**
11
- * Shows/hides a presentation.
34
+ * Indicates if the given string is an alphanumeric string.
35
+ * Note that some special characters are also allowed (-, _ , /, &, ?, =, ;) for the
36
+ * purpose of checking URIs.
37
+ * @param {string} unsafeText string to check
38
+ * @returns {boolean}
12 39
  */
13
-function setPresentationVisible(visible) {
40
+function isAlphanumeric(unsafeText) {
41
+    return alphanumRegex.test(unsafeText);
42
+}
14 43
 
15
-    if (visible) {
16
-        VideoLayout.setLargeVideoState("prezi");
44
+/**
45
+ * Returns the presentation id from the given url.
46
+ * @param {string} url Prezi link
47
+ * @returns {string} presentation id
48
+ */
49
+function getPresentationId (url) {
50
+    let presId = url.substring(url.indexOf("prezi.com/") + 10);
51
+    return presId.substring(0, presId.indexOf('/'));
52
+}
53
+
54
+/**
55
+ * Checks if given string is Prezi url.
56
+ * @param {string} url string to check.
57
+ * @returns {boolean}
58
+ */
59
+function isPreziLink(url) {
60
+    if (url.indexOf('http://prezi.com/') !== 0 && url.indexOf('https://prezi.com/') !== 0) {
61
+        return false;
17 62
     }
18
-    else {
19
-        VideoLayout.setLargeVideoState("video");
63
+
64
+    let presId = url.substring(url.indexOf("prezi.com/") + 10);
65
+    if (!isAlphanumeric(presId) || presId.indexOf('/') < 2) {
66
+        return false;
20 67
     }
68
+
69
+    return true;
21 70
 }
22 71
 
23
-var Prezi = {
72
+/**
73
+ * Notify user that other user if already sharing Prezi.
74
+ */
75
+function notifyOtherIsSharingPrezi() {
76
+    messageHandler.openMessageDialog(
77
+        "dialog.sharePreziTitle",
78
+        "dialog.sharePreziMsg"
79
+    );
80
+}
24 81
 
82
+/**
83
+ * Ask user if he want to close Prezi he's sharing.
84
+ */
85
+function proposeToClosePrezi() {
86
+    return new Promise(function (resolve, reject) {
87
+        messageHandler.openTwoButtonDialog(
88
+            "dialog.removePreziTitle",
89
+            null,
90
+            "dialog.removePreziMsg",
91
+            null,
92
+            false,
93
+            "dialog.Remove",
94
+            function(e,v,m,f) {
95
+                if (v) {
96
+                    resolve();
97
+                } else {
98
+                    reject();
99
+                }
100
+            }
101
+        );
25 102
 
26
-    /**
27
-     * Reloads the current presentation.
28
-     */
29
-    reloadPresentation: function() {
30
-        var iframe = document.getElementById(preziPlayer.options.preziId);
31
-        iframe.src = iframe.src;
32
-    },
103
+    });
104
+}
33 105
 
34
-    /**
35
-     * Returns <tt>true</tt> if the presentation is visible, <tt>false</tt> -
36
-     * otherwise.
37
-     */
38
-    isPresentationVisible: function () {
39
-        return ($('#presentation>iframe') != null
40
-                && $('#presentation>iframe').css('opacity') == 1);
41
-    },
106
+/**
107
+ * Ask user for Prezi url to share with others.
108
+ * Dialog validates client input to allow only Prezi urls.
109
+ */
110
+function requestPreziLink() {
111
+    const title = APP.translation.generateTranslationHTML("dialog.sharePreziTitle");
112
+    const cancelButton = APP.translation.generateTranslationHTML("dialog.Cancel");
113
+    const shareButton = APP.translation.generateTranslationHTML("dialog.Share");
114
+    const backButton = APP.translation.generateTranslationHTML("dialog.Back");
115
+    const linkError = APP.translation.generateTranslationHTML("dialog.preziLinkError");
116
+    const i18nOptions = {url: defaultPreziLink};
117
+    const defaultUrl = APP.translation.translateString(
118
+        "defaultPreziLink", i18nOptions
119
+    );
42 120
 
43
-    /**
44
-     * Opens the Prezi dialog, from which the user could choose a presentation
45
-     * to load.
46
-     */
47
-    openPreziDialog: function() {
48
-        var myprezi = APP.xmpp.getPrezi();
49
-        if (myprezi) {
50
-            messageHandler.openTwoButtonDialog("dialog.removePreziTitle",
51
-                null,
52
-                "dialog.removePreziMsg",
53
-                null,
54
-                false,
55
-                "dialog.Remove",
56
-                function(e,v,m,f) {
57
-                    if(v) {
58
-                        APP.xmpp.removePreziFromPresence();
121
+    return new Promise(function (resolve, reject) {
122
+        let dialog = messageHandler.openDialogWithStates({
123
+            state0: {
124
+                html:  `
125
+                    <h2>${title}</h2>
126
+                    <input name="preziUrl" type="text"
127
+                           data-i18n="[placeholder]defaultPreziLink"
128
+                           data-i18n-options="${JSON.stringify(i18nOptions)}"
129
+                           placeholder="${defaultUrl}" autofocus>`,
130
+                persistent: false,
131
+                buttons: [
132
+                    {title: cancelButton, value: false},
133
+                    {title: shareButton, value: true}
134
+                ],
135
+                focus: ':input:first',
136
+                defaultButton: 1,
137
+                submit: function (e, v, m, f) {
138
+                    e.preventDefault();
139
+                    if (!v) {
140
+                        reject('cancelled');
141
+                        dialog.close();
142
+                        return;
59 143
                     }
60
-                }
61
-            );
62
-        }
63
-        else if (preziPlayer != null) {
64
-            messageHandler.openTwoButtonDialog("dialog.sharePreziTitle",
65
-                null, "dialog.sharePreziMsg",
66
-                null,
67
-                false,
68
-                "dialog.Ok",
69
-                function(e,v,m,f) {
70
-                    $.prompt.close();
71
-                }
72
-            );
73
-        }
74
-        else {
75
-            var html = APP.translation.generateTranslationHTML(
76
-                "dialog.sharePreziTitle");
77
-            var cancelButton = APP.translation.generateTranslationHTML(
78
-                "dialog.Cancel");
79
-            var shareButton = APP.translation.generateTranslationHTML(
80
-                "dialog.Share");
81
-            var backButton = APP.translation.generateTranslationHTML(
82
-                "dialog.Back");
83
-            var buttons = [];
84
-            var buttons1 = [];
85
-            // Cancel button to both states
86
-            buttons.push({title: cancelButton, value: false});
87
-            buttons1.push({title: cancelButton, value: false});
88
-            // Share button
89
-            buttons.push({title: shareButton, value: true});
90
-            // Back button
91
-            buttons1.push({title: backButton, value: true});
92
-            var linkError = APP.translation.generateTranslationHTML(
93
-                "dialog.preziLinkError");
94
-            var defaultUrl = APP.translation.translateString("defaultPreziLink",
95
-                {url: "http://prezi.com/wz7vhjycl7e6/my-prezi"});
96
-            var openPreziState = {
97
-                state0: {
98
-                    html:   '<h2>' + html + '</h2>' +
99
-                            '<input name="preziUrl" type="text" ' +
100
-                            'data-i18n="[placeholder]defaultPreziLink" data-i18n-options=\'' +
101
-                            JSON.stringify({"url": "http://prezi.com/wz7vhjycl7e6/my-prezi"}) +
102
-                            '\' placeholder="' + defaultUrl + '" autofocus>',
103
-                    persistent: false,
104
-                    buttons: buttons,
105
-                    focus: ':input:first',
106
-                    defaultButton: 0,
107
-                    submit: function (e, v, m, f) {
108
-                        e.preventDefault();
109
-                        if(v)
110
-                        {
111
-                            var preziUrl = f.preziUrl;
112
-
113
-                            if (preziUrl)
114
-                            {
115
-                                var urlValue
116
-                                    = encodeURI(UIUtil.escapeHtml(preziUrl));
117
-
118
-                                if (urlValue.indexOf('http://prezi.com/') != 0
119
-                                    && urlValue.indexOf('https://prezi.com/') != 0)
120
-                                {
121
-                                    $.prompt.goToState('state1');
122
-                                    return false;
123
-                                }
124
-                                else {
125
-                                    var presIdTmp = urlValue.substring(
126
-                                            urlValue.indexOf("prezi.com/") + 10);
127
-                                    if (!isAlphanumeric(presIdTmp)
128
-                                            || presIdTmp.indexOf('/') < 2) {
129
-                                        $.prompt.goToState('state1');
130
-                                        return false;
131
-                                    }
132
-                                    else {
133
-                                        APP.xmpp.addToPresence("prezi", urlValue);
134
-                                        $.prompt.close();
135
-                                    }
136
-                                }
137
-                            }
138
-                        }
139
-                        else
140
-                            $.prompt.close();
144
+
145
+                    let preziUrl = f.preziUrl;
146
+                    if (!preziUrl) {
147
+                        return;
141 148
                     }
142
-                },
143
-                state1: {
144
-                    html:   '<h2>' + html + '</h2>' +
145
-                            linkError,
146
-                    persistent: false,
147
-                    buttons: buttons1,
148
-                    focus: ':input:first',
149
-                    defaultButton: 1,
150
-                    submit: function (e, v, m, f) {
151
-                        e.preventDefault();
152
-                        if (v === 0)
153
-                            $.prompt.close();
154
-                        else
155
-                            $.prompt.goToState('state0');
149
+
150
+                    let urlValue = encodeURI(UIUtil.escapeHtml(preziUrl));
151
+
152
+                    if (!isPreziLink(urlValue)) {
153
+                        dialog.goToState('state1');
154
+                        return false;
156 155
                     }
156
+
157
+                    resolve(urlValue);
158
+                    dialog.close();
157 159
                 }
158
-            };
159
-            messageHandler.openDialogWithStates(openPreziState);
160
-        }
161
-    }
160
+            },
161
+
162
+            state1: {
163
+                html: `<h2>${title}</h2> ${linkError}`,
164
+                persistent: false,
165
+                buttons: [
166
+                    {title: cancelButton, value: false},
167
+                    {title: backButton, value: true}
168
+                ],
169
+                focus: ':input:first',
170
+                defaultButton: 1,
171
+                submit: function (e, v, m, f) {
172
+                    e.preventDefault();
173
+                    if (v === 0) {
174
+                        reject();
175
+                        dialog.close();
176
+                    } else {
177
+                        dialog.goToState('state0');
178
+                    }
179
+                }
180
+            }
181
+        });
162 182
 
163
-};
183
+    });
184
+}
185
+
186
+export const PreziContainerType = "prezi";
164 187
 
165 188
 /**
166
- * A new presentation has been added.
167
- *
168
- * @param event the event indicating the add of a presentation
169
- * @param jid the jid from which the presentation was added
170
- * @param presUrl url of the presentation
171
- * @param currentSlide the current slide to which we should move
189
+ * Container for Prezi iframe.
172 190
  */
173
-function presentationAdded(event, jid, presUrl, currentSlide) {
174
-    console.log("presentation added", presUrl);
191
+class PreziContainer extends LargeContainer {
192
+
193
+    constructor ({preziId, isMy, slide, onSlideChanged}) {
194
+        super();
195
+        this.reloadBtn = $('#reloadPresentation');
196
+
197
+        let preziPlayer = new PreziPlayer(
198
+            'presentation', {
199
+                preziId,
200
+                width: DEFAULT_WIDTH,
201
+                height: DEFAULT_HEIGHT,
202
+                controls: isMy,
203
+                debug: true
204
+            }
205
+        );
206
+        this.preziPlayer = preziPlayer;
207
+        this.$iframe = $(preziPlayer.iframe);
175 208
 
176
-    var presId = getPresentationId(presUrl);
209
+        this.$iframe.attr('id', preziId);
177 210
 
178
-    var elementId = 'participant_'
179
-        + Strophe.getResourceFromJid(jid)
180
-        + '_' + presId;
211
+        preziPlayer.on(PreziPlayer.EVENT_STATUS, function({value}) {
212
+            console.log("prezi status", value);
213
+            if (value == PreziPlayer.STATUS_CONTENT_READY && !isMy) {
214
+                preziPlayer.flyToStep(slide);
215
+            }
216
+        });
181 217
 
182
-    VideoLayout.addPreziContainer(elementId);
218
+        preziPlayer.on(PreziPlayer.EVENT_CURRENT_STEP, function({value}) {
219
+            console.log("event value", value);
220
+            onSlideChanged(value);
221
+        });
222
+    }
183 223
 
184
-    var controlsEnabled = false;
185
-    if (jid === APP.xmpp.myJid())
186
-        controlsEnabled = true;
224
+    /**
225
+     * Change Prezi slide.
226
+     * @param {number} slide slide to show
227
+     */
228
+    goToSlide (slide) {
229
+        if (this.preziPlayer.getCurrentStep() === slide) {
230
+            return;
231
+        }
187 232
 
188
-    setPresentationVisible(true);
189
-    VideoLayout.setLargeVideoHover(
190
-        function (event) {
191
-            if (Prezi.isPresentationVisible()) {
192
-                var reloadButtonRight = window.innerWidth
193
-                    - $('#presentation>iframe').offset().left
194
-                    - $('#presentation>iframe').width();
233
+        this.preziPlayer.flyToStep(slide);
195 234
 
196
-                $('#reloadPresentation').css({  right: reloadButtonRight,
197
-                    display:'inline-block'});
198
-            }
199
-        },
200
-        function (event) {
201
-            if (!Prezi.isPresentationVisible())
202
-                $('#reloadPresentation').css({display:'none'});
203
-            else {
204
-                var e = event.toElement || event.relatedTarget;
205
-
206
-                if (e && e.id != 'reloadPresentation' && e.id != 'header')
207
-                    $('#reloadPresentation').css({display:'none'});
208
-            }
235
+        let animationStepsArray = this.preziPlayer.getAnimationCountOnSteps();
236
+        if (!animationStepsArray) {
237
+            return;
238
+        }
239
+
240
+        for (var i = 0; i < parseInt(animationStepsArray[slide]); i += 1) {
241
+            this.preziPlayer.flyToStep(slide, i);
242
+        }
243
+    }
244
+
245
+    /**
246
+     * Show or hide "reload presentation" button.
247
+     * @param {boolean} show
248
+     */
249
+    showReloadBtn (show) {
250
+        this.reloadBtn.css('display', show ? 'inline-block' : 'none');
251
+    }
252
+
253
+    show () {
254
+        return new Promise(resolve => {
255
+            this.$iframe.fadeIn(300, () => {
256
+                this.$iframe.css({opacity: 1});
257
+                ToolbarToggler.dockToolbar(true);
258
+                resolve();
259
+            });
209 260
         });
261
+    }
210 262
 
211
-    preziPlayer = new PreziPlayer(
212
-        'presentation',
213
-        {preziId: presId,
214
-            width: getPresentationWidth(),
215
-            height: getPresentationHeihgt(),
216
-            controls: controlsEnabled,
217
-            debug: true
263
+    hide () {
264
+        return new Promise(resolve => {
265
+            this.$iframe.fadeOut(300, () => {
266
+                this.$iframe.css({opacity: 0});
267
+                this.showReloadBtn(false);
268
+                ToolbarToggler.dockToolbar(false);
269
+                resolve();
270
+            });
218 271
         });
272
+    }
273
+
274
+    onHoverIn () {
275
+        let rightOffset = window.innerWidth - this.$iframe.offset().left - this.$iframe.width();
219 276
 
220
-    $('#presentation>iframe').attr('id', preziPlayer.options.preziId);
277
+        this.showReloadBtn(true);
278
+        this.reloadBtn.css('right', rightOffset);
279
+    }
221 280
 
222
-    preziPlayer.on(PreziPlayer.EVENT_STATUS, function(event) {
223
-        console.log("prezi status", event.value);
224
-        if (event.value == PreziPlayer.STATUS_CONTENT_READY) {
225
-            if (jid != APP.xmpp.myJid())
226
-                preziPlayer.flyToStep(currentSlide);
281
+    onHoverOut (event) {
282
+        let e = event.toElement || event.relatedTarget;
283
+
284
+        if (e && e.id != 'reloadPresentation' && e.id != 'header') {
285
+            this.showReloadBtn(false);
227 286
         }
228
-    });
287
+    }
229 288
 
230
-    preziPlayer.on(PreziPlayer.EVENT_CURRENT_STEP, function(event) {
231
-        console.log("event value", event.value);
232
-        APP.xmpp.addToPresence("preziSlide", event.value);
233
-    });
289
+    resize (containerWidth, containerHeight) {
290
+        let height = containerHeight - BottomToolbar.getFilmStripHeight();
291
+
292
+        let width = containerWidth;
234 293
 
235
-    $("#" + elementId).css( 'background-image',
236
-        'url(../images/avatarprezi.png)');
237
-    $("#" + elementId).click(
238
-        function () {
239
-            setPresentationVisible(true);
294
+        if (height < width / aspectRatio) {
295
+            width = Math.floor(height * aspectRatio);
240 296
         }
241
-    );
242
-};
243 297
 
244
-/**
245
- * A presentation has been removed.
246
- *
247
- * @param event the event indicating the remove of a presentation
248
- * @param jid the jid for which the presentation was removed
249
- * @param the url of the presentation
250
- */
251
-function presentationRemoved(event, jid, presUrl) {
252
-    console.log('presentation removed', presUrl);
253
-    var presId = getPresentationId(presUrl);
254
-    setPresentationVisible(false);
255
-    $('#participant_'
256
-        + Strophe.getResourceFromJid(jid)
257
-        + '_' + presId).remove();
258
-    $('#presentation>iframe').remove();
259
-    if (preziPlayer != null) {
260
-        preziPlayer.destroy();
261
-        preziPlayer = null;
298
+        this.$iframe.width(width).height(height);
262 299
     }
263
-};
264 300
 
265
-/**
266
- * Indicates if the given string is an alphanumeric string.
267
- * Note that some special characters are also allowed (-, _ , /, &, ?, =, ;) for the
268
- * purpose of checking URIs.
269
- */
270
-function isAlphanumeric(unsafeText) {
271
-    var regex = /^[a-z0-9-_\/&\?=;]+$/i;
272
-    return regex.test(unsafeText);
301
+    /**
302
+     * Close Prezi frame.
303
+     */
304
+    close () {
305
+        this.showReloadBtn(false);
306
+        this.preziPlayer.destroy();
307
+        this.$iframe.remove();
308
+    }
273 309
 }
274 310
 
275 311
 /**
276
- * Returns the presentation id from the given url.
312
+ * Manager of Prezi frames.
277 313
  */
278
-function getPresentationId (presUrl) {
279
-    var presIdTmp = presUrl.substring(presUrl.indexOf("prezi.com/") + 10);
280
-    return presIdTmp.substring(0, presIdTmp.indexOf('/'));
281
-}
314
+export default class PreziManager {
315
+    constructor (emitter) {
316
+        this.emitter = emitter;
282 317
 
283
-/**
284
- * Returns the presentation width.
285
- */
286
-function getPresentationWidth() {
287
-    var availableWidth = UIUtil.getAvailableVideoWidth();
288
-    var availableHeight = getPresentationHeihgt();
318
+        this.userId = null;
319
+        this.url = null;
320
+        this.prezi = null;
289 321
 
290
-    var aspectRatio = 16.0 / 9.0;
291
-    if (availableHeight < availableWidth / aspectRatio) {
292
-        availableWidth = Math.floor(availableHeight * aspectRatio);
322
+        $("#reloadPresentationLink").click(this.reloadPresentation.bind(this));
293 323
     }
294
-    return availableWidth;
295
-}
296 324
 
297
-/**
298
- * Returns the presentation height.
299
- */
300
-function getPresentationHeihgt() {
301
-    var remoteVideos = $('#remoteVideos');
302
-    return window.innerHeight - remoteVideos.outerHeight();
303
-}
325
+    get isPresenting () {
326
+        return !!this.userId;
327
+    }
304 328
 
305
-/**
306
- * Resizes the presentation iframe.
307
- */
308
-function resize() {
309
-    if ($('#presentation>iframe')) {
310
-        $('#presentation>iframe').width(getPresentationWidth());
311
-        $('#presentation>iframe').height(getPresentationHeihgt());
329
+    get isMyPrezi () {
330
+        return this.userId === APP.conference.localId;
312 331
     }
313
-}
314 332
 
315
-/**
316
- * Presentation has been removed.
317
- */
318
-$(document).bind('presentationremoved.muc', presentationRemoved);
333
+    /**
334
+     * Check if user is currently sharing.
335
+     * @param {string} id user id to check for
336
+     */
337
+    isSharing (id) {
338
+        return this.userId === id;
339
+    }
319 340
 
320
-/**
321
- * Presentation has been added.
322
- */
323
-$(document).bind('presentationadded.muc', presentationAdded);
341
+    handlePreziButtonClicked () {
342
+        if (!this.isPresenting) {
343
+            requestPreziLink().then(
344
+                url => this.emitter.emit(UIEvents.SHARE_PREZI, url, 0),
345
+                err => console.error('PREZI CANCELED', err)
346
+            );
347
+            return;
348
+        }
324 349
 
325
-/*
326
- * Indicates presentation slide change.
327
- */
328
-$(document).bind('gotoslide.muc', function (event, jid, presUrl, current) {
329
-    if (preziPlayer && preziPlayer.getCurrentStep() != current) {
330
-        preziPlayer.flyToStep(current);
350
+        if (this.isMyPrezi) {
351
+            proposeToClosePrezi().then(() => this.emitter.emit(UIEvents.STOP_SHARING_PREZI));
352
+        } else {
353
+            notifyOtherIsSharingPrezi();
354
+        }
355
+    }
356
+
357
+    /**
358
+     * Reload current Prezi frame.
359
+     */
360
+    reloadPresentation () {
361
+        if (!this.prezi) {
362
+            return;
363
+        }
364
+        let iframe = this.prezi.$iframe[0];
365
+        iframe.src = iframe.src;
366
+    }
367
+
368
+    /**
369
+     * Show Prezi. Create new Prezi if there is no Prezi yet.
370
+     * @param {string} id owner id
371
+     * @param {string} url Prezi url
372
+     * @param {number} slide slide to show
373
+     */
374
+    showPrezi (id, url, slide) {
375
+        if (!this.isPresenting) {
376
+            this.createPrezi(id, url, slide);
377
+        }
331 378
 
332
-        var animationStepsArray = preziPlayer.getAnimationCountOnSteps();
333
-        for (var i = 0; i < parseInt(animationStepsArray[current]); i++) {
334
-            preziPlayer.flyToStep(current, i);
379
+        if (this.userId === id && this.url === url) {
380
+            this.prezi.goToSlide(slide);
381
+        } else {
382
+            console.error(this.userId, id);
383
+            console.error(this.url, url);
384
+            throw new Error("unexpected presentation change");
335 385
         }
336 386
     }
337
-});
338 387
 
339
-$(window).resize(function () {
340
-    resize();
341
-});
388
+    /**
389
+     * Create new Prezi frame..
390
+     * @param {string} id owner id
391
+     * @param {string} url Prezi url
392
+     * @param {number} slide slide to show
393
+     */
394
+    createPrezi (id, url, slide) {
395
+        console.log("presentation added", url);
396
+
397
+        this.userId = id;
398
+        this.url = url;
399
+
400
+        let preziId = getPresentationId(url);
401
+        let elementId = `participant_${id}_${preziId}`;
402
+
403
+        this.$thumb = $(VideoLayout.addRemoteVideoContainer(elementId));
404
+        VideoLayout.resizeThumbnails();
405
+        this.$thumb.css({
406
+            'background-image': 'url(../images/avatarprezi.png)'
407
+        }).click(() => VideoLayout.showLargeVideoContainer(PreziContainerType, true));
408
+
409
+        this.prezi = new PreziContainer({
410
+            preziId,
411
+            isMy: this.isMyPrezi,
412
+            slide,
413
+            onSlideChanged: newSlide => {
414
+                if (this.isMyPrezi) {
415
+                    this.emitter.emit(UIEvents.SHARE_PREZI, url, newSlide);
416
+                }
417
+            }
418
+        });
419
+
420
+        VideoLayout.addLargeVideoContainer(PreziContainerType, this.prezi);
421
+        VideoLayout.showLargeVideoContainer(PreziContainerType, true);
422
+    }
342 423
 
343
-module.exports = Prezi;
424
+    /**
425
+     * Close Prezi.
426
+     * @param {string} id owner id
427
+     */
428
+    removePrezi (id) {
429
+        if (this.userId !== id) {
430
+            throw new Error(`cannot close presentation from ${this.userId} instead of ${id}`);
431
+        }
432
+
433
+        this.$thumb.remove();
434
+        this.$thumb = null;
435
+
436
+        // wait until Prezi is hidden, then remove it
437
+        VideoLayout.showLargeVideoContainer(PreziContainerType, false).then(() => {
438
+            console.log("presentation removed", this.url);
439
+
440
+            VideoLayout.removeLargeVideoContainer(PreziContainerType);
441
+
442
+            this.userId = null;
443
+            this.url = null;
444
+            this.prezi.close();
445
+            this.prezi = null;
446
+        });
447
+    }
448
+}

+ 276
- 284
modules/UI/prezi/PreziPlayer.js 查看文件

@@ -1,298 +1,290 @@
1
-/* global PreziPlayer */
2 1
 /* jshint -W101 */
3
-(function() {
4
-    "use strict";
5
-    var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
6 2
 
7
-    window.PreziPlayer = (function() {
8
-
9
-        PreziPlayer.API_VERSION = 1;
10
-        PreziPlayer.CURRENT_STEP = 'currentStep';
11
-        PreziPlayer.CURRENT_ANIMATION_STEP = 'currentAnimationStep';
12
-        PreziPlayer.CURRENT_OBJECT = 'currentObject';
13
-        PreziPlayer.STATUS_LOADING = 'loading';
14
-        PreziPlayer.STATUS_READY = 'ready';
15
-        PreziPlayer.STATUS_CONTENT_READY = 'contentready';
16
-        PreziPlayer.EVENT_CURRENT_STEP = "currentStepChange";
17
-        PreziPlayer.EVENT_CURRENT_ANIMATION_STEP = "currentAnimationStepChange";
18
-        PreziPlayer.EVENT_CURRENT_OBJECT = "currentObjectChange";
19
-        PreziPlayer.EVENT_STATUS = "statusChange";
20
-        PreziPlayer.EVENT_PLAYING = "isAutoPlayingChange";
21
-        PreziPlayer.EVENT_IS_MOVING = "isMovingChange";
22
-        PreziPlayer.domain = "https://prezi.com";
23
-        PreziPlayer.path = "/player/";
24
-        PreziPlayer.players = {};
25
-        PreziPlayer.binded_methods = ['changesHandler'];
26
-
27
-        PreziPlayer.createMultiplePlayers = function(optionArray){
28
-            for(var i=0; i<optionArray.length; i++) {
29
-                var optionSet = optionArray[i];
30
-                new PreziPlayer(optionSet.id, optionSet);
31
-            }
32
-        };
33
-
34
-        PreziPlayer.messageReceived = function(event){
35
-            var message, item, player;
36
-            try {
37
-                message = JSON.parse(event.data);
38
-                if (message.id && (player = PreziPlayer.players[message.id])) {
39
-                    if (player.options.debug === true) {
40
-                        if (console && console.log)
41
-                            console.log('received', message);
42
-                    }
43
-                    if (message.type === "changes") {
44
-                        player.changesHandler(message);
45
-                    }
46
-                    for (var i = 0; i < player.callbacks.length; i++) {
47
-                        item = player.callbacks[i];
48
-                        if (item && message.type === item.event) {
49
-                            item.callback(message);
50
-                        }
51
-                    }
52
-                }
53
-            } catch (e) { }
54
-        };
55
-
56
-/*jshint -W004 */
57
-        function PreziPlayer(id, options) {
58
-/*jshint +W004 */
59
-            var params, paramString = "", _this = this;
60
-            if (PreziPlayer.players[id]){
61
-                PreziPlayer.players[id].destroy();
3
+var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
4
+
5
+PreziPlayer.API_VERSION = 1;
6
+PreziPlayer.CURRENT_STEP = 'currentStep';
7
+PreziPlayer.CURRENT_ANIMATION_STEP = 'currentAnimationStep';
8
+PreziPlayer.CURRENT_OBJECT = 'currentObject';
9
+PreziPlayer.STATUS_LOADING = 'loading';
10
+PreziPlayer.STATUS_READY = 'ready';
11
+PreziPlayer.STATUS_CONTENT_READY = 'contentready';
12
+PreziPlayer.EVENT_CURRENT_STEP = "currentStepChange";
13
+PreziPlayer.EVENT_CURRENT_ANIMATION_STEP = "currentAnimationStepChange";
14
+PreziPlayer.EVENT_CURRENT_OBJECT = "currentObjectChange";
15
+PreziPlayer.EVENT_STATUS = "statusChange";
16
+PreziPlayer.EVENT_PLAYING = "isAutoPlayingChange";
17
+PreziPlayer.EVENT_IS_MOVING = "isMovingChange";
18
+PreziPlayer.domain = "https://prezi.com";
19
+PreziPlayer.path = "/player/";
20
+PreziPlayer.players = {};
21
+PreziPlayer.binded_methods = ['changesHandler'];
22
+
23
+PreziPlayer.createMultiplePlayers = function(optionArray){
24
+    for(var i=0; i<optionArray.length; i++) {
25
+        var optionSet = optionArray[i];
26
+        new PreziPlayer(optionSet.id, optionSet);
27
+    }
28
+};
29
+
30
+PreziPlayer.messageReceived = function(event){
31
+    var message, item, player;
32
+    try {
33
+        message = JSON.parse(event.data);
34
+        if (message.id && (player = PreziPlayer.players[message.id])) {
35
+            if (player.options.debug === true) {
36
+                if (console && console.log)
37
+                    console.log('received', message);
62 38
             }
63
-            for(var i=0; i<PreziPlayer.binded_methods.length; i++) {
64
-                var method_name = PreziPlayer.binded_methods[i];
65
-                _this[method_name] = __bind(_this[method_name], _this);
66
-            }
67
-            options = options || {};
68
-            this.options = options;
69
-            this.values = {'status': PreziPlayer.STATUS_LOADING};
70
-            this.values[PreziPlayer.CURRENT_STEP] = 0;
71
-            this.values[PreziPlayer.CURRENT_ANIMATION_STEP] = 0;
72
-            this.values[PreziPlayer.CURRENT_OBJECT] = null;
73
-            this.callbacks = [];
74
-            this.id = id;
75
-            this.embedTo = document.getElementById(id);
76
-            if (!this.embedTo) {
77
-                throw "The element id is not available.";
78
-            }
79
-            this.iframe = document.createElement('iframe');
80
-            params = [
81
-                { name: 'oid', value: options.preziId },
82
-                { name: 'explorable', value: options.explorable ? 1 : 0 },
83
-                { name: 'controls', value: options.controls ? 1 : 0 }
84
-            ];
85
-            for (i=0; i<params.length; i++) {
86
-                var param = params[i];
87
-                paramString += (i===0 ? "?" : "&") + param.name + "=" + param.value;
88
-            }
89
-            this.iframe.src = PreziPlayer.domain + PreziPlayer.path + paramString;
90
-            this.iframe.frameBorder = 0;
91
-            this.iframe.scrolling = "no";
92
-            this.iframe.width = options.width || 640;
93
-            this.iframe.height = options.height || 480;
94
-            this.embedTo.innerHTML = '';
95
-            // JITSI: IN CASE SOMETHING GOES WRONG.
96
-            try {
97
-                this.embedTo.appendChild(this.iframe);
98
-            }
99
-            catch (err) {
100
-                console.log("CATCH ERROR");
101
-            }
102
-
103
-            // JITSI: Increase interval from 200 to 500, which fixes prezi
104
-            // crashes for us.
105
-            this.initPollInterval = setInterval(function(){
106
-                _this.sendMessage({'action': 'init'});
107
-            }, 500);
108
-            PreziPlayer.players[id] = this;
109
-        }
110
-
111
-        PreziPlayer.prototype.changesHandler = function(message) {
112
-            var key, value, j, item;
113
-            if (this.initPollInterval) {
114
-                clearInterval(this.initPollInterval);
115
-                this.initPollInterval = false;
116
-            }
117
-            for (key in message.data) {
118
-                if (message.data.hasOwnProperty(key)){
119
-                    value = message.data[key];
120
-                    this.values[key] = value;
121
-                    for (j=0; j<this.callbacks.length; j++) {
122
-                        item = this.callbacks[j];
123
-                        if (item && item.event === key + "Change"){
124
-                            item.callback({type: item.event, value: value});
125
-                        }
126
-                    }
127
-                }
128
-            }
129
-        };
130
-
131
-        PreziPlayer.prototype.destroy = function() {
132
-            if (this.initPollInterval) {
133
-                clearInterval(this.initPollInterval);
134
-                this.initPollInterval = false;
135
-            }
136
-            this.embedTo.innerHTML = '';
137
-        };
138
-
139
-        PreziPlayer.prototype.sendMessage = function(message) {
140
-            if (this.options.debug === true) {
141
-                if (console && console.log) console.log('sent', message);
142
-            }
143
-            message.version = PreziPlayer.API_VERSION;
144
-            message.id = this.id;
145
-            return this.iframe.contentWindow.postMessage(JSON.stringify(message), '*');
146
-        };
147
-
148
-        PreziPlayer.prototype.nextStep = /* nextStep is DEPRECATED */
149
-        PreziPlayer.prototype.flyToNextStep = function() {
150
-            return this.sendMessage({
151
-                'action': 'present',
152
-                'data': ['moveToNextStep']
153
-            });
154
-        };
155
-
156
-        PreziPlayer.prototype.previousStep = /* previousStep is DEPRECATED */
157
-        PreziPlayer.prototype.flyToPreviousStep = function() {
158
-            return this.sendMessage({
159
-                'action': 'present',
160
-                'data': ['moveToPrevStep']
161
-            });
162
-        };
163
-
164
-        PreziPlayer.prototype.toStep = /* toStep is DEPRECATED */
165
-        PreziPlayer.prototype.flyToStep = function(step, animation_step) {
166
-            var obj = this;
167
-            // check animation_step
168
-            if (animation_step > 0 &&
169
-                obj.values.animationCountOnSteps &&
170
-                obj.values.animationCountOnSteps[step] <= animation_step) {
171
-                animation_step = obj.values.animationCountOnSteps[step];
39
+            if (message.type === "changes") {
40
+                player.changesHandler(message);
172 41
             }
173
-            // jump to animation steps by calling flyToNextStep()
174
-            function doAnimationSteps() {
175
-                if (obj.values.isMoving) {
176
-                    setTimeout(doAnimationSteps, 100); // wait until the flight ends
177
-                    return;
178
-                }
179
-                while (animation_step-- > 0) {
180
-                    obj.flyToNextStep(); // do the animation steps
42
+            for (var i = 0; i < player.callbacks.length; i++) {
43
+                item = player.callbacks[i];
44
+                if (item && message.type === item.event) {
45
+                    item.callback(message);
181 46
                 }
182 47
             }
183
-            setTimeout(doAnimationSteps, 200); // 200ms is the internal "reporting" time
184
-            // jump to the step
185
-            return this.sendMessage({
186
-                'action': 'present',
187
-                'data': ['moveToStep', step]
188
-            });
189
-        };
190
-
191
-        PreziPlayer.prototype.toObject = /* toObject is DEPRECATED */
192
-        PreziPlayer.prototype.flyToObject = function(objectId) {
193
-            return this.sendMessage({
194
-                'action': 'present',
195
-                'data': ['moveToObject', objectId]
196
-            });
197
-        };
198
-
199
-        PreziPlayer.prototype.play = function(defaultDelay) {
200
-            return this.sendMessage({
201
-                'action': 'present',
202
-                'data': ['startAutoPlay', defaultDelay]
203
-            });
204
-        };
205
-
206
-        PreziPlayer.prototype.stop = function() {
207
-            return this.sendMessage({
208
-                'action': 'present',
209
-                'data': ['stopAutoPlay']
210
-            });
211
-        };
212
-
213
-        PreziPlayer.prototype.pause = function(defaultDelay) {
214
-            return this.sendMessage({
215
-                'action': 'present',
216
-                'data': ['pauseAutoPlay', defaultDelay]
217
-            });
218
-        };
219
-
220
-        PreziPlayer.prototype.getCurrentStep = function() {
221
-            return this.values.currentStep;
222
-        };
223
-
224
-        PreziPlayer.prototype.getCurrentAnimationStep = function() {
225
-            return this.values.currentAnimationStep;
226
-        };
227
-
228
-        PreziPlayer.prototype.getCurrentObject = function() {
229
-            return this.values.currentObject;
230
-        };
231
-
232
-        PreziPlayer.prototype.getStatus = function() {
233
-            return this.values.status;
234
-        };
235
-
236
-        PreziPlayer.prototype.isPlaying = function() {
237
-            return this.values.isAutoPlaying;
238
-        };
239
-
240
-        PreziPlayer.prototype.getStepCount = function() {
241
-            return this.values.stepCount;
242
-        };
243
-
244
-        PreziPlayer.prototype.getAnimationCountOnSteps = function() {
245
-            return this.values.animationCountOnSteps;
246
-        };
247
-
248
-        PreziPlayer.prototype.getTitle = function() {
249
-            return this.values.title;
250
-        };
251
-
252
-        PreziPlayer.prototype.setDimensions = function(dims) {
253
-            for (var parameter in dims) {
254
-                this.iframe[parameter] = dims[parameter];
255
-            }
256
-        };
257
-
258
-        PreziPlayer.prototype.getDimensions = function() {
259
-            return {
260
-                width: parseInt(this.iframe.width, 10),
261
-                height: parseInt(this.iframe.height, 10)
262
-            };
263
-        };
264
-
265
-        PreziPlayer.prototype.on = function(event, callback) {
266
-            this.callbacks.push({
267
-                event: event,
268
-                callback: callback
269
-            });
270
-        };
48
+        }
49
+    } catch (e) { }
50
+};
271 51
 
272
-        PreziPlayer.prototype.off = function(event, callback) {
273
-            var j, item;
274
-            if (event === undefined) {
275
-                this.callbacks = [];
276
-            }
277
-            j = this.callbacks.length;
278
-            while (j--) {
52
+/*jshint -W004 */
53
+function PreziPlayer(id, options) {
54
+    /*jshint +W004 */
55
+    var params, paramString = "", _this = this;
56
+    if (PreziPlayer.players[id]){
57
+        PreziPlayer.players[id].destroy();
58
+    }
59
+    for(var i=0; i<PreziPlayer.binded_methods.length; i++) {
60
+        var method_name = PreziPlayer.binded_methods[i];
61
+        _this[method_name] = __bind(_this[method_name], _this);
62
+    }
63
+    options = options || {};
64
+    this.options = options;
65
+    this.values = {'status': PreziPlayer.STATUS_LOADING};
66
+    this.values[PreziPlayer.CURRENT_STEP] = 0;
67
+    this.values[PreziPlayer.CURRENT_ANIMATION_STEP] = 0;
68
+    this.values[PreziPlayer.CURRENT_OBJECT] = null;
69
+    this.callbacks = [];
70
+    this.id = id;
71
+    this.embedTo = document.getElementById(id);
72
+    if (!this.embedTo) {
73
+        throw "The element id is not available.";
74
+    }
75
+    this.iframe = document.createElement('iframe');
76
+    params = [
77
+        { name: 'oid', value: options.preziId },
78
+        { name: 'explorable', value: options.explorable ? 1 : 0 },
79
+        { name: 'controls', value: options.controls ? 1 : 0 }
80
+    ];
81
+    for (i=0; i<params.length; i++) {
82
+        var param = params[i];
83
+        paramString += (i===0 ? "?" : "&") + param.name + "=" + param.value;
84
+    }
85
+    this.iframe.src = PreziPlayer.domain + PreziPlayer.path + paramString;
86
+    this.iframe.frameBorder = 0;
87
+    this.iframe.scrolling = "no";
88
+    this.iframe.width = options.width || 640;
89
+    this.iframe.height = options.height || 480;
90
+    this.embedTo.innerHTML = '';
91
+    // JITSI: IN CASE SOMETHING GOES WRONG.
92
+    try {
93
+        this.embedTo.appendChild(this.iframe);
94
+    }
95
+    catch (err) {
96
+        console.log("CATCH ERROR");
97
+    }
98
+
99
+    // JITSI: Increase interval from 200 to 500, which fixes prezi
100
+    // crashes for us.
101
+    this.initPollInterval = setInterval(function(){
102
+        _this.sendMessage({'action': 'init'});
103
+    }, 500);
104
+    PreziPlayer.players[id] = this;
105
+}
106
+
107
+PreziPlayer.prototype.changesHandler = function(message) {
108
+    var key, value, j, item;
109
+    if (this.initPollInterval) {
110
+        clearInterval(this.initPollInterval);
111
+        this.initPollInterval = false;
112
+    }
113
+    for (key in message.data) {
114
+        if (message.data.hasOwnProperty(key)){
115
+            value = message.data[key];
116
+            this.values[key] = value;
117
+            for (j=0; j<this.callbacks.length; j++) {
279 118
                 item = this.callbacks[j];
280
-                if (item && item.event === event && (callback === undefined || item.callback === callback)){
281
-                    this.callbacks.splice(j, 1);
119
+                if (item && item.event === key + "Change"){
120
+                    item.callback({type: item.event, value: value});
282 121
                 }
283 122
             }
284
-        };
285
-
286
-        if (window.addEventListener) {
287
-            window.addEventListener('message', PreziPlayer.messageReceived, false);
288
-        } else {
289
-            window.attachEvent('onmessage', PreziPlayer.messageReceived);
290 123
         }
124
+    }
125
+};
126
+
127
+PreziPlayer.prototype.destroy = function() {
128
+    if (this.initPollInterval) {
129
+        clearInterval(this.initPollInterval);
130
+        this.initPollInterval = false;
131
+    }
132
+    this.embedTo.innerHTML = '';
133
+};
134
+
135
+PreziPlayer.prototype.sendMessage = function(message) {
136
+    if (this.options.debug === true) {
137
+        if (console && console.log) console.log('sent', message);
138
+    }
139
+    message.version = PreziPlayer.API_VERSION;
140
+    message.id = this.id;
141
+    return this.iframe.contentWindow.postMessage(JSON.stringify(message), '*');
142
+};
143
+
144
+PreziPlayer.prototype.nextStep = /* nextStep is DEPRECATED */
145
+PreziPlayer.prototype.flyToNextStep = function() {
146
+    return this.sendMessage({
147
+        'action': 'present',
148
+        'data': ['moveToNextStep']
149
+    });
150
+};
151
+
152
+PreziPlayer.prototype.previousStep = /* previousStep is DEPRECATED */
153
+PreziPlayer.prototype.flyToPreviousStep = function() {
154
+    return this.sendMessage({
155
+        'action': 'present',
156
+        'data': ['moveToPrevStep']
157
+    });
158
+};
159
+
160
+PreziPlayer.prototype.toStep = /* toStep is DEPRECATED */
161
+PreziPlayer.prototype.flyToStep = function(step, animation_step) {
162
+    var obj = this;
163
+    // check animation_step
164
+    if (animation_step > 0 &&
165
+        obj.values.animationCountOnSteps &&
166
+        obj.values.animationCountOnSteps[step] <= animation_step) {
167
+        animation_step = obj.values.animationCountOnSteps[step];
168
+    }
169
+    // jump to animation steps by calling flyToNextStep()
170
+    function doAnimationSteps() {
171
+        if (obj.values.isMoving) {
172
+            setTimeout(doAnimationSteps, 100); // wait until the flight ends
173
+            return;
174
+        }
175
+        while (animation_step-- > 0) {
176
+            obj.flyToNextStep(); // do the animation steps
177
+        }
178
+    }
179
+    setTimeout(doAnimationSteps, 200); // 200ms is the internal "reporting" time
180
+    // jump to the step
181
+    return this.sendMessage({
182
+        'action': 'present',
183
+        'data': ['moveToStep', step]
184
+    });
185
+};
186
+
187
+PreziPlayer.prototype.toObject = /* toObject is DEPRECATED */
188
+PreziPlayer.prototype.flyToObject = function(objectId) {
189
+    return this.sendMessage({
190
+        'action': 'present',
191
+        'data': ['moveToObject', objectId]
192
+    });
193
+};
194
+
195
+PreziPlayer.prototype.play = function(defaultDelay) {
196
+    return this.sendMessage({
197
+        'action': 'present',
198
+        'data': ['startAutoPlay', defaultDelay]
199
+    });
200
+};
201
+
202
+PreziPlayer.prototype.stop = function() {
203
+    return this.sendMessage({
204
+        'action': 'present',
205
+        'data': ['stopAutoPlay']
206
+    });
207
+};
208
+
209
+PreziPlayer.prototype.pause = function(defaultDelay) {
210
+    return this.sendMessage({
211
+        'action': 'present',
212
+        'data': ['pauseAutoPlay', defaultDelay]
213
+    });
214
+};
215
+
216
+PreziPlayer.prototype.getCurrentStep = function() {
217
+    return this.values.currentStep;
218
+};
219
+
220
+PreziPlayer.prototype.getCurrentAnimationStep = function() {
221
+    return this.values.currentAnimationStep;
222
+};
223
+
224
+PreziPlayer.prototype.getCurrentObject = function() {
225
+    return this.values.currentObject;
226
+};
227
+
228
+PreziPlayer.prototype.getStatus = function() {
229
+    return this.values.status;
230
+};
231
+
232
+PreziPlayer.prototype.isPlaying = function() {
233
+    return this.values.isAutoPlaying;
234
+};
235
+
236
+PreziPlayer.prototype.getStepCount = function() {
237
+    return this.values.stepCount;
238
+};
239
+
240
+PreziPlayer.prototype.getAnimationCountOnSteps = function() {
241
+    return this.values.animationCountOnSteps;
242
+};
243
+
244
+PreziPlayer.prototype.getTitle = function() {
245
+    return this.values.title;
246
+};
247
+
248
+PreziPlayer.prototype.setDimensions = function(dims) {
249
+    for (var parameter in dims) {
250
+        this.iframe[parameter] = dims[parameter];
251
+    }
252
+};
253
+
254
+PreziPlayer.prototype.getDimensions = function() {
255
+    return {
256
+        width: parseInt(this.iframe.width, 10),
257
+        height: parseInt(this.iframe.height, 10)
258
+    };
259
+};
260
+
261
+PreziPlayer.prototype.on = function(event, callback) {
262
+    this.callbacks.push({
263
+        event: event,
264
+        callback: callback
265
+    });
266
+};
267
+
268
+PreziPlayer.prototype.off = function(event, callback) {
269
+    var j, item;
270
+    if (event === undefined) {
271
+        this.callbacks = [];
272
+    }
273
+    j = this.callbacks.length;
274
+    while (j--) {
275
+        item = this.callbacks[j];
276
+        if (item && item.event === event && (callback === undefined || item.callback === callback)){
277
+            this.callbacks.splice(j, 1);
278
+        }
279
+    }
280
+};
291 281
 
292
-        return PreziPlayer;
293
-
294
-    })();
282
+if (window.addEventListener) {
283
+    window.addEventListener('message', PreziPlayer.messageReceived, false);
284
+} else {
285
+    window.attachEvent('onmessage', PreziPlayer.messageReceived);
286
+}
295 287
 
296
-})();
288
+window.PreziPlayer = PreziPlayer;
297 289
 
298
-module.exports = PreziPlayer;
290
+export default PreziPlayer;

+ 105
- 117
modules/UI/side_pannels/SidePanelToggler.js 查看文件

@@ -1,102 +1,100 @@
1 1
 /* global require, $ */
2
-var Chat = require("./chat/Chat");
3
-var ContactList = require("./contactlist/ContactList");
4
-var Settings = require("./../../settings/Settings");
5
-var SettingsMenu = require("./settings/SettingsMenu");
6
-var VideoLayout = require("../videolayout/VideoLayout");
7
-var ToolbarToggler = require("../toolbars/ToolbarToggler");
8
-var UIUtil = require("../util/UIUtil");
9
-var LargeVideo = require("../videolayout/LargeVideo");
2
+import Chat from "./chat/Chat";
3
+import ContactList from "./contactlist/ContactList";
4
+import Settings from "./../../settings/Settings";
5
+import SettingsMenu from "./settings/SettingsMenu";
6
+import VideoLayout from "../videolayout/VideoLayout";
7
+import ToolbarToggler from "../toolbars/ToolbarToggler";
8
+import UIUtil from "../util/UIUtil";
9
+
10
+const buttons = {
11
+    '#chatspace': '#chatBottomButton',
12
+    '#contactlist': '#contactListButton',
13
+    '#settingsmenu': '#toolbar_button_settings'
14
+};
15
+
16
+var currentlyOpen = null;
10 17
 
11 18
 /**
12
- * Toggler for the chat, contact list, settings menu, etc..
19
+ * Toggles the windows in the side panel
20
+ * @param object the window that should be shown
21
+ * @param selector the selector for the element containing the panel
22
+ * @param onOpenComplete function to be called when the panel is opened
23
+ * @param onOpen function to be called if the window is going to be opened
24
+ * @param onClose function to be called if the window is going to be closed
13 25
  */
14
-var PanelToggler = (function(my) {
26
+function toggle (object, selector, onOpenComplete, onOpen, onClose) {
27
+    UIUtil.buttonClick(buttons[selector], "active");
28
+
29
+    if (object.isVisible()) {
30
+        $("#toast-container").animate({
31
+            right: 5
32
+        }, {
33
+            queue: false,
34
+            duration: 500
35
+        });
36
+        $(selector).hide("slide", {
37
+            direction: "right",
38
+            queue: false,
39
+            duration: 500
40
+        });
41
+        if(typeof onClose === "function") {
42
+            onClose();
43
+        }
15 44
 
16
-    var currentlyOpen = null;
17
-    var buttons = {
18
-        '#chatspace': '#chatBottomButton',
19
-        '#contactlist': '#contactListButton',
20
-        '#settingsmenu': '#toolbar_button_settings'
21
-    };
45
+        currentlyOpen = null;
46
+    } else {
47
+        // Undock the toolbar when the chat is shown and if we're in a
48
+        // video mode.
49
+        if (VideoLayout.isLargeVideoVisible()) {
50
+            ToolbarToggler.dockToolbar(false);
51
+        }
22 52
 
23
-    /**
24
-     * Toggles the windows in the side panel
25
-     * @param object the window that should be shown
26
-     * @param selector the selector for the element containing the panel
27
-     * @param onOpenComplete function to be called when the panel is opened
28
-     * @param onOpen function to be called if the window is going to be opened
29
-     * @param onClose function to be called if the window is going to be closed
30
-     */
31
-    var toggle = function(object, selector, onOpenComplete, onOpen, onClose) {
32
-        UIUtil.buttonClick(buttons[selector], "active");
33
-
34
-        if (object.isVisible()) {
35
-            $("#toast-container").animate({
36
-                    right: '5px'
37
-                },
38
-                {
39
-                    queue: false,
40
-                    duration: 500
41
-                });
42
-            $(selector).hide("slide", {
43
-                direction: "right",
44
-                queue: false,
45
-                duration: 500
46
-            });
47
-            if(typeof onClose === "function") {
48
-                onClose();
49
-            }
50
-
51
-            currentlyOpen = null;
53
+        if (currentlyOpen) {
54
+            var current = $(currentlyOpen);
55
+            UIUtil.buttonClick(buttons[currentlyOpen], "active");
56
+            current.css('z-index', 4);
57
+            setTimeout(function () {
58
+                current.css('display', 'none');
59
+                current.css('z-index', 5);
60
+            }, 500);
52 61
         }
53
-        else {
54
-            // Undock the toolbar when the chat is shown and if we're in a
55
-            // video mode.
56
-            if (LargeVideo.isLargeVideoVisible()) {
57
-                ToolbarToggler.dockToolbar(false);
58
-            }
59
-
60
-            if(currentlyOpen) {
61
-                var current = $(currentlyOpen);
62
-                UIUtil.buttonClick(buttons[currentlyOpen], "active");
63
-                current.css('z-index', 4);
64
-                setTimeout(function () {
65
-                    current.css('display', 'none');
66
-                    current.css('z-index', 5);
67
-                }, 500);
68
-            }
69
-
70
-            $("#toast-container").animate({
71
-                    right: (PanelToggler.getPanelSize()[0] + 5) + 'px'
72
-                },
73
-                {
74
-                    queue: false,
75
-                    duration: 500
76
-                });
77
-            $(selector).show("slide", {
78
-                direction: "right",
79
-                queue: false,
80
-                duration: 500,
81
-                complete: onOpenComplete
82
-            });
83
-            if(typeof onOpen === "function") {
84
-                onOpen();
85
-            }
86
-
87
-            currentlyOpen = selector;
62
+
63
+        $("#toast-container").animate({
64
+            right: (UIUtil.getSidePanelSize()[0] + 5)
65
+        }, {
66
+            queue: false,
67
+            duration: 500
68
+        });
69
+        $(selector).show("slide", {
70
+            direction: "right",
71
+            queue: false,
72
+            duration: 500,
73
+            complete: onOpenComplete
74
+        });
75
+        if(typeof onOpen === "function") {
76
+            onOpen();
88 77
         }
89
-    };
78
+
79
+        currentlyOpen = selector;
80
+    }
81
+}
82
+
83
+/**
84
+ * Toggler for the chat, contact list, settings menu, etc..
85
+ */
86
+var PanelToggler = {
90 87
 
91 88
     /**
92 89
      * Opens / closes the chat area.
93 90
      */
94
-    my.toggleChat = function() {
95
-        var chatCompleteFunction = Chat.isVisible() ?
96
-            function() {} : function () {
97
-            Chat.scrollChatToBottom();
98
-            $('#chatspace').trigger('shown');
99
-        };
91
+    toggleChat () {
92
+        var chatCompleteFunction = Chat.isVisible()
93
+            ? function () {}
94
+            : function () {
95
+                Chat.scrollChatToBottom();
96
+                $('#chatspace').trigger('shown');
97
+            };
100 98
 
101 99
         VideoLayout.resizeVideoArea(!Chat.isVisible(), chatCompleteFunction);
102 100
 
@@ -112,16 +110,24 @@ var PanelToggler = (function(my) {
112 110
                 }
113 111
             },
114 112
             null,
115
-            Chat.resizeChat,
113
+            () => this.resizeChat(),
116 114
             null);
117
-    };
115
+    },
116
+
117
+    resizeChat () {
118
+        let [width, height] = UIUtil.getSidePanelSize();
119
+        Chat.resizeChat(width, height);
120
+    },
118 121
 
119 122
     /**
120 123
      * Opens / closes the contact list area.
121 124
      */
122
-    my.toggleContactList = function () {
123
-        var completeFunction = ContactList.isVisible() ?
124
-            function() {} : function () { $('#contactlist').trigger('shown');};
125
+    toggleContactList () {
126
+        var completeFunction = ContactList.isVisible()
127
+            ? function () {}
128
+            : function () {
129
+                $('#contactlist').trigger('shown');
130
+            };
125 131
         VideoLayout.resizeVideoArea(!ContactList.isVisible(), completeFunction);
126 132
 
127 133
         toggle(ContactList,
@@ -131,12 +137,12 @@ var PanelToggler = (function(my) {
131 137
                 ContactList.setVisualNotification(false);
132 138
             },
133 139
             null);
134
-    };
140
+    },
135 141
 
136 142
     /**
137 143
      * Opens / closes the settings menu
138 144
      */
139
-    my.toggleSettingsMenu = function() {
145
+    toggleSettingsMenu () {
140 146
         VideoLayout.resizeVideoArea(!SettingsMenu.isVisible(), function (){});
141 147
         toggle(SettingsMenu,
142 148
             '#settingsmenu',
@@ -147,31 +153,13 @@ var PanelToggler = (function(my) {
147 153
                 $('#setEmail').get(0).value = settings.email;
148 154
             },
149 155
             null);
150
-    };
151
-
152
-    /**
153
-     * Returns the size of the side panel.
154
-     */
155
-    my.getPanelSize = function () {
156
-        var availableHeight = window.innerHeight;
157
-        var availableWidth = window.innerWidth;
156
+    },
158 157
 
159
-        var panelWidth = 200;
160
-        if (availableWidth * 0.2 < 200) {
161
-            panelWidth = availableWidth * 0.2;
162
-        }
163
-
164
-        return [panelWidth, availableHeight];
165
-    };
166
-
167
-    my.isVisible = function() {
158
+    isVisible () {
168 159
         return (Chat.isVisible() ||
169 160
                 ContactList.isVisible() ||
170 161
                 SettingsMenu.isVisible());
171
-    };
172
-
173
-    return my;
174
-
175
-}(PanelToggler || {}));
162
+    }
163
+};
176 164
 
177
-module.exports = PanelToggler;
165
+export default PanelToggler;

+ 45
- 59
modules/UI/side_pannels/chat/Chat.js 查看文件

@@ -1,11 +1,13 @@
1
-/* global APP, $, Util, nickname:true */
2
-var Replacement = require("./Replacement");
3
-var CommandsProcessor = require("./Commands");
4
-var ToolbarToggler = require("../../toolbars/ToolbarToggler");
1
+/* global APP, $ */
2
+
3
+import {processReplacements, linkify} from './Replacement';
4
+import CommandsProcessor from './Commands';
5
+import ToolbarToggler from '../../toolbars/ToolbarToggler';
6
+
7
+import UIUtil from '../../util/UIUtil';
8
+import UIEvents from '../../../../service/UI/UIEvents';
9
+
5 10
 var smileys = require("./smileys.json").smileys;
6
-var NicknameHandler = require("../../util/NicknameHandler");
7
-var UIUtil = require("../../util/UIUtil");
8
-var UIEvents = require("../../../../service/UI/UIEvents");
9 11
 
10 12
 var notificationInterval = false;
11 13
 var unreadMessages = 0;
@@ -165,28 +167,21 @@ function resizeChatConversation() {
165 167
 /**
166 168
  * Chat related user interface.
167 169
  */
168
-var Chat = (function (my) {
170
+var Chat = {
169 171
     /**
170 172
      * Initializes chat related interface.
171 173
      */
172
-    my.init = function () {
173
-        if(NicknameHandler.getNickname())
174
+    init (eventEmitter) {
175
+        if (APP.settings.getDisplayName()) {
174 176
             Chat.setChatConversationMode(true);
175
-        NicknameHandler.addListener(UIEvents.NICKNAME_CHANGED,
176
-            function (nickname) {
177
-                Chat.setChatConversationMode(true);
178
-            });
177
+        }
179 178
 
180 179
         $('#nickinput').keydown(function (event) {
181 180
             if (event.keyCode === 13) {
182 181
                 event.preventDefault();
183 182
                 var val = UIUtil.escapeHtml(this.value);
184 183
                 this.value = '';
185
-                if (!NicknameHandler.getNickname()) {
186
-                    NicknameHandler.setNickname(val);
187
-
188
-                    return;
189
-                }
184
+                eventEmitter.emit(UIEvents.NICKNAME_CHANGED, val);
190 185
             }
191 186
         });
192 187
 
@@ -197,14 +192,12 @@ var Chat = (function (my) {
197 192
                 var value = this.value;
198 193
                 usermsg.val('').trigger('autosize.resize');
199 194
                 this.focus();
200
-                var command = new CommandsProcessor(value);
201
-                if(command.isCommand()) {
195
+                var command = new CommandsProcessor(value, eventEmitter);
196
+                if (command.isCommand()) {
202 197
                     command.processCommand();
203
-                }
204
-                else {
198
+                } else {
205 199
                     var message = UIUtil.escapeHtml(value);
206
-                    APP.xmpp.sendChatMessage(message,
207
-                                             NicknameHandler.getNickname());
200
+                    eventEmitter.emit(UIEvents.MESSAGE_CREATED, message);
208 201
                 }
209 202
             }
210 203
         });
@@ -222,19 +215,17 @@ var Chat = (function (my) {
222 215
             });
223 216
 
224 217
         addSmileys();
225
-    };
218
+    },
226 219
 
227 220
     /**
228 221
      * Appends the given message to the chat conversation.
229 222
      */
230
-    my.updateChatConversation =
231
-        function (from, displayName, message, myjid, stamp) {
223
+    updateChatConversation (id, displayName, message, stamp) {
232 224
         var divClassName = '';
233 225
 
234
-        if (APP.xmpp.myJid() === from) {
226
+        if (APP.conference.isLocalId(id)) {
235 227
             divClassName = "localuser";
236
-        }
237
-        else {
228
+        } else {
238 229
             divClassName = "remoteuser";
239 230
 
240 231
             if (!Chat.isVisible()) {
@@ -250,7 +241,7 @@ var Chat = (function (my) {
250 241
         var escMessage = message.replace(/</g, '&lt;').
251 242
             replace(/>/g, '&gt;').replace(/\n/g, '<br/>');
252 243
         var escDisplayName = UIUtil.escapeHtml(displayName);
253
-        message = Replacement.processReplacements(escMessage);
244
+        message = processReplacements(escMessage);
254 245
 
255 246
         var messageContainer =
256 247
             '<div class="chatmessage">'+
@@ -263,14 +254,14 @@ var Chat = (function (my) {
263 254
         $('#chatconversation').append(messageContainer);
264 255
         $('#chatconversation').animate(
265 256
                 { scrollTop: $('#chatconversation')[0].scrollHeight}, 1000);
266
-    };
257
+    },
267 258
 
268 259
     /**
269 260
      * Appends error message to the conversation
270 261
      * @param errorMessage the received error message.
271 262
      * @param originalText the original message.
272 263
      */
273
-    my.chatAddError = function(errorMessage, originalText) {
264
+    chatAddError (errorMessage, originalText) {
274 265
         errorMessage = UIUtil.escapeHtml(errorMessage);
275 266
         originalText = UIUtil.escapeHtml(originalText);
276 267
 
@@ -281,28 +272,28 @@ var Chat = (function (my) {
281 272
             (errorMessage? (' Reason: ' + errorMessage) : '') +  '</div>');
282 273
         $('#chatconversation').animate(
283 274
             { scrollTop: $('#chatconversation')[0].scrollHeight}, 1000);
284
-    };
275
+    },
285 276
 
286 277
     /**
287 278
      * Sets the subject to the UI
288 279
      * @param subject the subject
289 280
      */
290
-    my.chatSetSubject = function(subject) {
291
-        if (subject)
281
+    setSubject (subject) {
282
+        if (subject) {
292 283
             subject = subject.trim();
293
-        $('#subject').html(Replacement.linkify(UIUtil.escapeHtml(subject)));
294
-        if(subject === "") {
295
-            $("#subject").css({display: "none"});
296 284
         }
297
-        else {
285
+        $('#subject').html(linkify(UIUtil.escapeHtml(subject)));
286
+        if (subject) {
298 287
             $("#subject").css({display: "block"});
288
+        } else {
289
+            $("#subject").css({display: "none"});
299 290
         }
300
-    };
291
+    },
301 292
 
302 293
     /**
303 294
      * Sets the chat conversation mode.
304 295
      */
305
-    my.setChatConversationMode = function (isConversationMode) {
296
+    setChatConversationMode (isConversationMode) {
306 297
         if (isConversationMode) {
307 298
             $('#nickname').css({visibility: 'hidden'});
308 299
             $('#chatconversation').css({visibility: 'visible'});
@@ -310,42 +301,37 @@ var Chat = (function (my) {
310 301
             $('#smileysarea').css({visibility: 'visible'});
311 302
             $('#usermsg').focus();
312 303
         }
313
-    };
304
+    },
314 305
 
315 306
     /**
316 307
      * Resizes the chat area.
317 308
      */
318
-    my.resizeChat = function () {
319
-        var chatSize = require("../SidePanelToggler").getPanelSize();
320
-
321
-        $('#chatspace').width(chatSize[0]);
322
-        $('#chatspace').height(chatSize[1]);
309
+    resizeChat (width, height) {
310
+        $('#chatspace').width(width).height(height);
323 311
 
324 312
         resizeChatConversation();
325
-    };
313
+    },
326 314
 
327 315
     /**
328 316
      * Indicates if the chat is currently visible.
329 317
      */
330
-    my.isVisible = function () {
318
+    isVisible () {
331 319
         return $('#chatspace').is(":visible");
332
-    };
320
+    },
333 321
     /**
334 322
      * Shows and hides the window with the smileys
335 323
      */
336
-    my.toggleSmileys = toggleSmileys;
324
+    toggleSmileys,
337 325
 
338 326
     /**
339 327
      * Scrolls chat to the bottom.
340 328
      */
341
-    my.scrollChatToBottom = function() {
329
+    scrollChatToBottom () {
342 330
         setTimeout(function () {
343 331
             $('#chatconversation').scrollTop(
344 332
                 $('#chatconversation')[0].scrollHeight);
345 333
         }, 5);
346
-    };
347
-
334
+    }
335
+};
348 336
 
349
-    return my;
350
-}(Chat || {}));
351
-module.exports = Chat;
337
+export default Chat;

+ 11
- 8
modules/UI/side_pannels/chat/Commands.js 查看文件

@@ -1,12 +1,13 @@
1
-/* global APP, require */
2
-var UIUtil = require("../../util/UIUtil");
1
+/* global APP */
2
+import UIUtil from '../../util/UIUtil';
3
+import UIEvents from '../../../../service/UI/UIEvents';
3 4
 
4 5
 /**
5 6
  * List with supported commands. The keys are the names of the commands and
6 7
  * the value is the function that processes the message.
7 8
  * @type {{String: function}}
8 9
  */
9
-var commands = {
10
+const commands = {
10 11
     "topic" : processTopic
11 12
 };
12 13
 
@@ -29,9 +30,9 @@ function getCommand(message) {
29 30
  * Processes the data for topic command.
30 31
  * @param commandArguments the arguments of the topic command.
31 32
  */
32
-function processTopic(commandArguments) {
33
+function processTopic(commandArguments, emitter) {
33 34
     var topic = UIUtil.escapeHtml(commandArguments);
34
-    APP.xmpp.setSubject(topic);
35
+    emitter.emit(UIEvents.SUBJECT_CHANGED, topic);
35 36
 }
36 37
 
37 38
 /**
@@ -40,9 +41,11 @@ function processTopic(commandArguments) {
40 41
  * @param message the message
41 42
  * @constructor
42 43
  */
43
-function CommandsProcessor(message) {
44
+function CommandsProcessor(message, emitter) {
44 45
     var command = getCommand(message);
45 46
 
47
+    this.emitter = emitter;
48
+
46 49
     /**
47 50
      * Returns the name of the command.
48 51
      * @returns {String} the command
@@ -80,7 +83,7 @@ CommandsProcessor.prototype.processCommand = function() {
80 83
     if(!this.isCommand())
81 84
         return;
82 85
 
83
-    commands[this.getCommand()](this.getArgument());
86
+    commands[this.getCommand()](this.getArgument(), this.emitter);
84 87
 };
85 88
 
86
-module.exports = CommandsProcessor;
89
+export default CommandsProcessor;

+ 4
- 11
modules/UI/side_pannels/chat/Replacement.js 查看文件

@@ -1,10 +1,10 @@
1 1
 /* jshint -W101 */
2 2
 var Smileys = require("./smileys.json");
3
+
3 4
 /**
4 5
  * Processes links and smileys in "body"
5 6
  */
6
-function processReplacements(body)
7
-{
7
+export function processReplacements(body) {
8 8
     //make links clickable
9 9
     body = linkify(body);
10 10
 
@@ -18,8 +18,7 @@ function processReplacements(body)
18 18
  * Finds and replaces all links in the links in "body"
19 19
  * with their <a href=""></a>
20 20
  */
21
-function linkify(inputText)
22
-{
21
+export function linkify(inputText) {
23 22
     var replacedText, replacePattern1, replacePattern2, replacePattern3;
24 23
 
25 24
     //URLs starting with http://, https://, or ftp://
@@ -40,8 +39,7 @@ function linkify(inputText)
40 39
 /**
41 40
  * Replaces common smiley strings with images
42 41
  */
43
-function smilify(body)
44
-{
42
+function smilify(body) {
45 43
     if(!body) {
46 44
         return body;
47 45
     }
@@ -56,8 +54,3 @@ function smilify(body)
56 54
 
57 55
     return body;
58 56
 }
59
-
60
-module.exports = {
61
-    processReplacements: processReplacements,
62
-    linkify: linkify
63
-};

+ 55
- 75
modules/UI/side_pannels/contactlist/ContactList.js 查看文件

@@ -1,8 +1,9 @@
1
-/* global $, APP, Strophe */
2
-var Avatar = require('../../avatar/Avatar');
1
+/* global $, APP */
2
+import Avatar from '../../avatar/Avatar';
3
+import UIEvents from '../../../../service/UI/UIEvents';
3 4
 
4
-var numberOfContacts = 0;
5
-var notificationInterval;
5
+let numberOfContacts = 0;
6
+let notificationInterval;
6 7
 
7 8
 /**
8 9
  * Updates the number of participants in the contact list button and sets
@@ -30,7 +31,7 @@ function updateNumberOfParticipants(delta) {
30 31
  * @return {object} the newly created avatar element
31 32
  */
32 33
 function createAvatar(jid) {
33
-    var avatar = document.createElement('img');
34
+    let avatar = document.createElement('img');
34 35
     avatar.className = "icon-avatar avatar";
35 36
     avatar.src = Avatar.getAvatarUrl(jid);
36 37
 
@@ -43,12 +44,12 @@ function createAvatar(jid) {
43 44
  * @param displayName the display name to set
44 45
  */
45 46
 function createDisplayNameParagraph(key, displayName) {
46
-    var p = document.createElement('p');
47
-    if(displayName)
48
-        p.innerText = displayName;
49
-    else if(key) {
47
+    let p = document.createElement('p');
48
+    if (displayName) {
49
+        p.innerHTML = displayName;
50
+    } else if(key) {
50 51
         p.setAttribute("data-i18n",key);
51
-        p.innerText = APP.translation.translateString(key);
52
+        p.innerHTML = APP.translation.translateString(key);
52 53
     }
53 54
 
54 55
     return p;
@@ -64,93 +65,79 @@ function stopGlowing(glower) {
64 65
     }
65 66
 }
66 67
 
68
+function getContactEl (id) {
69
+    return $(`#contacts>li[id="${id}"]`);
70
+}
71
+
72
+function contactElExists (id) {
73
+    return getContactEl(id).length > 0;
74
+}
75
+
67 76
 /**
68 77
  * Contact list.
69 78
  */
70 79
 var ContactList = {
80
+    init (emitter) {
81
+        this.emitter = emitter;
82
+    },
71 83
     /**
72 84
      * Indicates if the chat is currently visible.
73 85
      *
74 86
      * @return <tt>true</tt> if the chat is currently visible, <tt>false</tt> -
75 87
      * otherwise
76 88
      */
77
-    isVisible: function () {
89
+    isVisible () {
78 90
         return $('#contactlist').is(":visible");
79 91
     },
80 92
 
81 93
     /**
82
-     * Adds a contact for the given peerJid if such doesn't yet exist.
94
+     * Adds a contact for the given id.
83 95
      *
84
-     * @param peerJid the peerJid corresponding to the contact
85 96
      */
86
-    ensureAddContact: function (peerJid) {
87
-        var resourceJid = Strophe.getResourceFromJid(peerJid);
88
-
89
-        var contact = $('#contacts>li[id="' + resourceJid + '"]');
90
-
91
-        if (!contact || contact.length <= 0)
92
-            ContactList.addContact(peerJid);
93
-    },
94
-
95
-    /**
96
-     * Adds a contact for the given peer jid.
97
-     *
98
-     * @param peerJid the jid of the contact to add
99
-     */
100
-    addContact: function (peerJid) {
101
-        var resourceJid = Strophe.getResourceFromJid(peerJid);
102
-
103
-        var contactlist = $('#contacts');
97
+    addContact (id) {
98
+        let contactlist = $('#contacts');
104 99
 
105
-        var newContact = document.createElement('li');
106
-        newContact.id = resourceJid;
100
+        let newContact = document.createElement('li');
101
+        newContact.id = id;
107 102
         newContact.className = "clickable";
108
-        newContact.onclick = function (event) {
103
+        newContact.onclick = (event) => {
109 104
             if (event.currentTarget.className === "clickable") {
110
-                $(ContactList).trigger('contactclicked', [peerJid]);
105
+                this.emitter.emit(UIEvents.CONTACT_CLICKED, id);
111 106
             }
112 107
         };
113 108
 
114
-        newContact.appendChild(createAvatar(peerJid));
109
+        newContact.appendChild(createAvatar(id));
115 110
         newContact.appendChild(createDisplayNameParagraph("participant"));
116 111
 
117
-        if (resourceJid === APP.xmpp.myResource()) {
112
+        if (APP.conference.isLocalId(id)) {
118 113
             contactlist.prepend(newContact);
119
-        }
120
-        else {
114
+        } else {
121 115
             contactlist.append(newContact);
122 116
         }
123 117
         updateNumberOfParticipants(1);
124 118
     },
125 119
 
126 120
     /**
127
-     * Removes a contact for the given peer jid.
121
+     * Removes a contact for the given id.
128 122
      *
129
-     * @param peerJid the peerJid corresponding to the contact to remove
130 123
      */
131
-    removeContact: function (peerJid) {
132
-        var resourceJid = Strophe.getResourceFromJid(peerJid);
133
-
134
-        var contact = $('#contacts>li[id="' + resourceJid + '"]');
135
-
136
-        if (contact && contact.length > 0) {
137
-            var contactlist = $('#contactlist>ul');
138
-
139
-            contactlist.get(0).removeChild(contact.get(0));
124
+    removeContact (id) {
125
+        let contact = getContactEl(id);
140 126
 
127
+        if (contact.length > 0) {
128
+            contact.remove();
141 129
             updateNumberOfParticipants(-1);
142 130
         }
143 131
     },
144 132
 
145
-    setVisualNotification: function (show, stopGlowingIn) {
146
-        var glower = $('#contactListButton');
133
+    setVisualNotification (show, stopGlowingIn) {
134
+        let glower = $('#contactListButton');
147 135
 
148 136
         if (show && !notificationInterval) {
149 137
             notificationInterval = window.setInterval(function () {
150 138
                 glower.toggleClass('active glowing');
151 139
             }, 800);
152
-        }
153
-        else if (!show && notificationInterval) {
140
+        } else if (!show && notificationInterval) {
154 141
             stopGlowing(glower);
155 142
         }
156 143
         if (stopGlowingIn) {
@@ -160,35 +147,28 @@ var ContactList = {
160 147
         }
161 148
     },
162 149
 
163
-    setClickable: function (resourceJid, isClickable) {
164
-        var contact = $('#contacts>li[id="' + resourceJid + '"]');
165
-        if (isClickable) {
166
-            contact.addClass('clickable');
167
-        } else {
168
-            contact.removeClass('clickable');
169
-        }
150
+    setClickable (id, isClickable) {
151
+        getContactEl(id).toggleClass('clickable', isClickable);
170 152
     },
171 153
 
172
-    onDisplayNameChange: function (peerJid, displayName) {
173
-        if (peerJid === 'localVideoContainer')
174
-            peerJid = APP.xmpp.myJid();
175
-
176
-        var resourceJid = Strophe.getResourceFromJid(peerJid);
177
-
178
-        var contactName = $('#contacts #' + resourceJid + '>p');
154
+    onDisplayNameChange (id, displayName) {
155
+        if (id === 'localVideoContainer') {
156
+            id = APP.conference.localId;
157
+        }
158
+        let contactName = $(`#contacts #${id}>p`);
179 159
 
180
-        if (contactName && displayName && displayName.length > 0)
160
+        if (displayName) {
181 161
             contactName.html(displayName);
162
+        }
182 163
     },
183 164
 
184
-    userAvatarChanged: function (resourceJid, avatarUrl) {
165
+    changeUserAvatar (id, avatarUrl) {
185 166
         // set the avatar in the contact list
186
-        var contact = $('#' + resourceJid + '>img');
187
-        if (contact && contact.length > 0) {
188
-            contact.get(0).src = avatarUrl;
167
+        let contact = $(`#${id}>img`);
168
+        if (contact.length > 0) {
169
+            contact.attr('src', avatarUrl);
189 170
         }
190
-
191 171
     }
192 172
 };
193 173
 
194
-module.exports = ContactList;
174
+export default ContactList;

+ 55
- 66
modules/UI/side_pannels/settings/SettingsMenu.js 查看文件

@@ -1,12 +1,12 @@
1 1
 /* global APP, $ */
2
-var Avatar = require("../../avatar/Avatar");
3
-var Settings = require("./../../../settings/Settings");
4
-var UIUtil = require("../../util/UIUtil");
5
-var languages = require("../../../../service/translation/languages");
2
+import UIUtil from "../../util/UIUtil";
3
+import UIEvents from "../../../../service/UI/UIEvents";
4
+import languages from "../../../../service/translation/languages";
5
+import Settings from '../../../settings/Settings';
6 6
 
7 7
 function generateLanguagesSelectBox() {
8 8
     var currentLang = APP.translation.getCurrentLanguage();
9
-    var html = "<select id=\"languages_selectbox\">";
9
+    var html = '<select id="languages_selectbox">';
10 10
     var langArray = languages.getLanguages();
11 11
     for(var i = 0; i < langArray.length; i++) {
12 12
         var lang = langArray[i];
@@ -22,32 +22,54 @@ function generateLanguagesSelectBox() {
22 22
 }
23 23
 
24 24
 
25
-var SettingsMenu = {
25
+export default {
26
+    init (emitter) {
27
+        function update() {
28
+            let displayName = UIUtil.escapeHtml($('#setDisplayName').val());
26 29
 
27
-    init: function () {
28
-        var startMutedSelector = $("#startMutedOptions");
29
-        startMutedSelector.before(generateLanguagesSelectBox());
30
-        APP.translation.translateElement($("#languages_selectbox"));
31
-        $('#settingsmenu>input').keyup(function(event){
32
-            if(event.keyCode === 13) {//enter
33
-                SettingsMenu.update();
30
+            if (displayName && Settings.getDisplayName() !== displayName) {
31
+                emitter.emit(UIEvents.NICKNAME_CHANGED, displayName);
34 32
             }
35
-        });
36 33
 
37
-        if (APP.xmpp.isModerator()) {
38
-            startMutedSelector.css("display", "block");
39
-        }
40
-        else {
41
-            startMutedSelector.css("display", "none");
34
+            let language = $("#languages_selectbox").val();
35
+            if (language !== Settings.getLanguage()) {
36
+                emitter.emit(UIEvents.LANG_CHANGED, language);
37
+            }
38
+
39
+            let email = UIUtil.escapeHtml($('#setEmail').val());
40
+            if (email !== Settings.getEmail()) {
41
+                emitter.emit(UIEvents.EMAIL_CHANGED, email);
42
+            }
43
+
44
+            let startAudioMuted = $("#startAudioMuted").is(":checked");
45
+            let startVideoMuted = $("#startVideoMuted").is(":checked");
46
+            if (startAudioMuted !== APP.conference.startAudioMuted
47
+                || startVideoMuted !== APP.conference.startVideoMuted) {
48
+                emitter.emit(
49
+                    UIEvents.START_MUTED_CHANGED,
50
+                    startAudioMuted,
51
+                    startVideoMuted
52
+                );
53
+            }
42 54
         }
43 55
 
44
-        $("#updateSettings").click(function () {
45
-            SettingsMenu.update();
56
+        let startMutedBlock = $("#startMutedOptions");
57
+        startMutedBlock.before(generateLanguagesSelectBox());
58
+        APP.translation.translateElement($("#languages_selectbox"));
59
+
60
+        this.onRoleChanged();
61
+        this.onStartMutedChanged();
62
+
63
+        $("#updateSettings").click(update);
64
+        $('#settingsmenu>input').keyup(function(event){
65
+            if (event.keyCode === 13) {//enter
66
+                update();
67
+            }
46 68
         });
47 69
     },
48 70
 
49
-    onRoleChanged: function () {
50
-        if(APP.xmpp.isModerator()) {
71
+    onRoleChanged () {
72
+        if(APP.conference.isModerator) {
51 73
             $("#startMutedOptions").css("display", "block");
52 74
         }
53 75
         else {
@@ -55,55 +77,22 @@ var SettingsMenu = {
55 77
         }
56 78
     },
57 79
 
58
-    setStartMuted: function (audio, video) {
59
-        $("#startAudioMuted").attr("checked", audio);
60
-        $("#startVideoMuted").attr("checked", video);
61
-    },
62
-
63
-    update: function() {
64
-        var newDisplayName =
65
-            UIUtil.escapeHtml($('#setDisplayName').get(0).value);
66
-        var newEmail = UIUtil.escapeHtml($('#setEmail').get(0).value);
67
-
68
-        if(newDisplayName) {
69
-            var displayName = Settings.setDisplayName(newDisplayName);
70
-            APP.xmpp.addToPresence("displayName", displayName, true);
71
-        }
72
-
73
-        var language = $("#languages_selectbox").val();
74
-        APP.translation.setLanguage(language);
75
-        Settings.setLanguage(language);
76
-
77
-        APP.xmpp.addToPresence("email", newEmail);
78
-        var email = Settings.setEmail(newEmail);
79
-
80
-        var startAudioMuted = ($("#startAudioMuted").is(":checked"));
81
-        var startVideoMuted = ($("#startVideoMuted").is(":checked"));
82
-        APP.xmpp.addToPresence("startMuted",
83
-            [startAudioMuted, startVideoMuted]);
84
-
85
-        Avatar.setUserAvatar(APP.xmpp.myJid(), email);
80
+    onStartMutedChanged () {
81
+        $("#startAudioMuted").attr("checked", APP.conference.startAudioMuted);
82
+        $("#startVideoMuted").attr("checked", APP.conference.startVideoMuted);
86 83
     },
87 84
 
88
-    isVisible: function() {
85
+    isVisible () {
89 86
         return $('#settingsmenu').is(':visible');
90 87
     },
91 88
 
92
-    setDisplayName: function(newDisplayName) {
93
-        var displayName = Settings.setDisplayName(newDisplayName);
94
-        $('#setDisplayName').get(0).value = displayName;
95
-    },
96
-
97
-    onDisplayNameChange: function(peerJid, newDisplayName) {
98
-        if(peerJid === 'localVideoContainer' ||
99
-            peerJid === APP.xmpp.myJid()) {
100
-            this.setDisplayName(newDisplayName);
89
+    onDisplayNameChange (id, newDisplayName) {
90
+        if(id === 'localVideoContainer' || APP.conference.isLocalId(id)) {
91
+            $('#setDisplayName').val(newDisplayName);
101 92
         }
102 93
     },
103
-    changeAvatar: function (thumbUrl) {
104
-        $('#avatar').get(0).src = thumbUrl;
94
+
95
+    changeAvatar (avatarUrl) {
96
+        $('#avatar').attr('src', avatarUrl);
105 97
     }
106 98
 };
107
-
108
-
109
-module.exports = SettingsMenu;

+ 92
- 49
modules/UI/toolbars/BottomToolbar.js 查看文件

@@ -1,66 +1,109 @@
1 1
 /* global $ */
2
-var PanelToggler = require("../side_pannels/SidePanelToggler");
3
-var UIUtil = require("../util/UIUtil");
4
-var AnalyticsAdapter = require("../../statistics/AnalyticsAdapter");
5
-var UIEvents = require("../../../service/UI/UIEvents");
2
+import UIUtil from '../util/UIUtil';
3
+import UIEvents from '../../../service/UI/UIEvents';
4
+import AnalyticsAdapter from '../../statistics/AnalyticsAdapter';
6 5
 
7
-var eventEmitter = null;
6
+const defaultBottomToolbarButtons = {
7
+    'chat':      '#bottom_toolbar_chat',
8
+    'contacts':  '#bottom_toolbar_contact_list',
9
+    'filmstrip': '#bottom_toolbar_film_strip'
10
+};
8 11
 
9
-var buttonHandlers = {
10
-    "bottom_toolbar_contact_list": function () {
11
-        AnalyticsAdapter.sendEvent('bottomtoolbar.contacts.toggled');
12
-        BottomToolbar.toggleContactList();
13
-    },
14
-    "bottom_toolbar_film_strip": function () {
15
-        AnalyticsAdapter.sendEvent('bottomtoolbar.filmstrip.toggled');
16
-        BottomToolbar.toggleFilmStrip();
12
+const BottomToolbar = {
13
+    init () {
14
+        this.filmStrip = $('#remoteVideos');
15
+        this.toolbar = $('#bottomToolbar');
17 16
     },
18
-    "bottom_toolbar_chat": function () {
19
-        AnalyticsAdapter.sendEvent('bottomtoolbar.chat.toggled');
20
-        BottomToolbar.toggleChat();
21
-    }
22
-};
23 17
 
18
+    setupListeners (emitter) {
19
+        UIUtil.hideDisabledButtons(defaultBottomToolbarButtons);
24 20
 
25
-var defaultBottomToolbarButtons = {
26
-    'chat': '#bottom_toolbar_chat',
27
-    'contacts': '#bottom_toolbar_contact_list',
28
-    'filmstrip': '#bottom_toolbar_film_strip'
29
-};
21
+        const buttonHandlers = {
22
+            "bottom_toolbar_contact_list": function () {
23
+                AnalyticsAdapter.sendEvent('bottomtoolbar.contacts.toggled');
24
+                emitter.emit(UIEvents.TOGGLE_CONTACT_LIST);
25
+            },
26
+            "bottom_toolbar_film_strip": function () {
27
+                AnalyticsAdapter.sendEvent('bottomtoolbar.filmstrip.toggled');
28
+                emitter.emit(UIEvents.TOGGLE_FILM_STRIP);
29
+            },
30
+            "bottom_toolbar_chat": function () {
31
+                AnalyticsAdapter.sendEvent('bottomtoolbar.chat.toggled');
32
+                emitter.emit(UIEvents.TOGGLE_CHAT);
33
+            }
34
+        };
30 35
 
36
+        Object.keys(buttonHandlers).forEach(
37
+            buttonId => $(`#${buttonId}`).click(buttonHandlers[buttonId])
38
+        );
39
+    },
31 40
 
32
-var BottomToolbar = (function (my) {
33
-    my.init = function (emitter) {
34
-        eventEmitter = emitter;
35
-        UIUtil.hideDisabledButtons(defaultBottomToolbarButtons);
41
+    toggleFilmStrip () {
42
+        this.filmStrip.toggleClass("hidden");
43
+    },
36 44
 
37
-        for(var k in buttonHandlers)
38
-            $("#" + k).click(buttonHandlers[k]);
39
-    };
45
+    isFilmStripVisible () {
46
+        return !this.filmStrip.hasClass('hidden');
47
+    },
48
+
49
+    setupFilmStripOnly () {
50
+        this.filmStrip.css({
51
+            padding: "0px 0px 18px 0px",
52
+            right: 0
53
+        });
54
+    },
55
+
56
+    getFilmStripHeight () {
57
+        if (this.isFilmStripVisible()) {
58
+            return this.filmStrip.outerHeight();
59
+        } else {
60
+            return 0;
61
+        }
62
+    },
40 63
 
41
-    my.toggleChat = function() {
42
-        PanelToggler.toggleChat();
43
-    };
64
+    getFilmStripWidth () {
65
+        return this.filmStrip.width();
66
+    },
44 67
 
45
-    my.toggleContactList = function() {
46
-        PanelToggler.toggleContactList();
47
-    };
68
+    resizeThumbnails (thumbWidth, thumbHeight,
69
+                      animate = false, forceUpdate = false) {
70
+        return new Promise(resolve => {
71
+            this.filmStrip.animate({
72
+                // adds 2 px because of small video 1px border
73
+                height: thumbHeight + 2
74
+            }, {
75
+                queue: false,
76
+                duration: animate ? 500 : 0
77
+            });
48 78
 
49
-    my.toggleFilmStrip = function() {
50
-        var filmstrip = $("#remoteVideos");
51
-        filmstrip.toggleClass("hidden");
79
+            this.getThumbs(!forceUpdate).animate({
80
+                height: thumbHeight,
81
+                width: thumbWidth
82
+            }, {
83
+                queue: false,
84
+                duration: animate ? 500 : 0,
85
+                complete:  resolve
86
+            });
52 87
 
53
-        eventEmitter.emit(  UIEvents.FILM_STRIP_TOGGLED,
54
-                            filmstrip.hasClass("hidden"));
55
-    };
88
+            if (!animate) {
89
+                resolve();
90
+            }
91
+        });
92
+    },
56 93
 
57
-    $(document).bind("remotevideo.resized", function (event, width, height) {
58
-        var bottom = (height - $('#bottomToolbar').outerHeight())/2 + 18;
94
+    resizeToolbar (thumbWidth, thumbHeight) {
95
+        let bottom = (thumbHeight - this.toolbar.outerHeight())/2 + 18;
96
+        this.toolbar.css({bottom});
97
+    },
59 98
 
60
-        $('#bottomToolbar').css({bottom: bottom + 'px'});
61
-    });
99
+    getThumbs (only_visible = false) {
100
+        let selector = 'span';
101
+        if (only_visible) {
102
+            selector += ':visible';
103
+        }
62 104
 
63
-    return my;
64
-}(BottomToolbar || {}));
105
+        return this.filmStrip.children(selector);
106
+    }
107
+};
65 108
 
66
-module.exports = BottomToolbar;
109
+export default BottomToolbar;

+ 190
- 539
modules/UI/toolbars/Toolbar.js 查看文件

@@ -1,70 +1,134 @@
1
-/* global APP, $, buttonClick, config, lockRoom, interfaceConfig, setSharedKey,
2
- Util */
1
+/* global APP, $, config, interfaceConfig */
3 2
 /* jshint -W101 */
4
-var messageHandler = require("../util/MessageHandler");
5
-var BottomToolbar = require("./BottomToolbar");
6
-var Prezi = require("../prezi/Prezi");
7
-var Etherpad = require("../etherpad/Etherpad");
8
-var PanelToggler = require("../side_pannels/SidePanelToggler");
9
-var Authentication = require("../authentication/Authentication");
10
-var UIUtil = require("../util/UIUtil");
11
-var AuthenticationEvents
12
-    = require("../../../service/authentication/AuthenticationEvents");
13
-var AnalyticsAdapter = require("../../statistics/AnalyticsAdapter");
14
-var Feedback = require("../Feedback");
15
-
16
-var roomUrl = null;
17
-var sharedKey = '';
18
-var UI = null;
19
-var recordingToaster = null;
20
-
21
-var buttonHandlers = {
3
+import messageHandler from '../util/MessageHandler';
4
+import UIUtil from '../util/UIUtil';
5
+import AnalyticsAdapter from '../../statistics/AnalyticsAdapter';
6
+import UIEvents from '../../../service/UI/UIEvents';
7
+
8
+let roomUrl = null;
9
+let recordingToaster = null;
10
+let emitter = null;
11
+
12
+
13
+/**
14
+ * Opens the invite link dialog.
15
+ */
16
+function openLinkDialog () {
17
+    let inviteAttributes;
18
+
19
+    if (roomUrl === null) {
20
+        inviteAttributes = 'data-i18n="[value]roomUrlDefaultMsg" value="' +
21
+            APP.translation.translateString("roomUrlDefaultMsg") + '"';
22
+    } else {
23
+        inviteAttributes = "value=\"" + encodeURI(roomUrl) + "\"";
24
+    }
25
+    messageHandler.openTwoButtonDialog(
26
+        "dialog.shareLink", null, null,
27
+        `<input id="inviteLinkRef" type="text" ${inviteAttributes} onclick="this.select();" readonly>`,
28
+        false, "dialog.Invite",
29
+        function (e, v) {
30
+            if (v && roomUrl) {
31
+                emitter.emit(UIEvents.USER_INVITED, roomUrl);
32
+            }
33
+        },
34
+        function (event) {
35
+            if (roomUrl) {
36
+                document.getElementById('inviteLinkRef').select();
37
+            } else {
38
+                if (event && event.target) {
39
+                    $(event.target).find('button[value=true]').prop('disabled', true);
40
+                }
41
+            }
42
+        }
43
+    );
44
+}
45
+
46
+// Sets the state of the recording button
47
+function setRecordingButtonState (recordingState) {
48
+    let selector = $('#toolbar_button_record');
49
+
50
+    if (recordingState === 'on') {
51
+        selector.removeClass("icon-recEnable");
52
+        selector.addClass("icon-recEnable active");
53
+
54
+        $("#largeVideo").toggleClass("videoMessageFilter", true);
55
+        let recordOnKey = "recording.on";
56
+        $('#videoConnectionMessage').attr("data-i18n", recordOnKey);
57
+        $('#videoConnectionMessage').text(APP.translation.translateString(recordOnKey));
58
+
59
+        setTimeout(function(){
60
+            $("#largeVideo").toggleClass("videoMessageFilter", false);
61
+            $('#videoConnectionMessage').css({display: "none"});
62
+        }, 1500);
63
+
64
+        recordingToaster = messageHandler.notify(
65
+            null, "recording.toaster", null,
66
+            null, null,
67
+            {timeOut: 0, closeButton: null, tapToDismiss: false}
68
+        );
69
+    } else if (recordingState === 'off') {
70
+        selector.removeClass("icon-recEnable active");
71
+        selector.addClass("icon-recEnable");
72
+
73
+        $("#largeVideo").toggleClass("videoMessageFilter", false);
74
+        $('#videoConnectionMessage').css({display: "none"});
75
+
76
+        if (recordingToaster) {
77
+            messageHandler.remove(recordingToaster);
78
+        }
79
+    } else if (recordingState === 'pending') {
80
+        selector.removeClass("icon-recEnable active");
81
+        selector.addClass("icon-recEnable");
82
+
83
+        $("#largeVideo").toggleClass("videoMessageFilter", true);
84
+        let recordPendingKey = "recording.pending";
85
+        $('#videoConnectionMessage').attr("data-i18n", recordPendingKey);
86
+        $('#videoConnectionMessage').text(APP.translation.translateString(recordPendingKey));
87
+        $('#videoConnectionMessage').css({display: "block"});
88
+    }
89
+}
90
+
91
+const buttonHandlers = {
22 92
     "toolbar_button_mute": function () {
23
-        if (APP.RTC.localAudio.isMuted()) {
93
+        if (APP.conference.audioMuted) {
24 94
             AnalyticsAdapter.sendEvent('toolbar.audio.unmuted');
95
+            emitter.emit(UIEvents.AUDIO_MUTED, false);
25 96
         } else {
26 97
             AnalyticsAdapter.sendEvent('toolbar.audio.muted');
98
+            emitter.emit(UIEvents.AUDIO_MUTED, true);
27 99
         }
28
-        return APP.UI.toggleAudio();
29 100
     },
30 101
     "toolbar_button_camera": function () {
31
-        if (APP.RTC.localVideo.isMuted()) {
102
+        if (APP.conference.videoMuted) {
32 103
             AnalyticsAdapter.sendEvent('toolbar.video.enabled');
104
+            emitter.emit(UIEvents.VIDEO_MUTED, false);
33 105
         } else {
34 106
             AnalyticsAdapter.sendEvent('toolbar.video.disabled');
107
+            emitter.emit(UIEvents.VIDEO_MUTED, true);
35 108
         }
36
-        return APP.UI.toggleVideo();
37 109
     },
38
-    /*"toolbar_button_authentication": function () {
39
-        return Toolbar.authenticateClicked();
40
-    },*/
41 110
     "toolbar_button_record": function () {
42 111
         AnalyticsAdapter.sendEvent('toolbar.recording.toggled');
43
-        return toggleRecording();
112
+        emitter.emit(UIEvents.RECORDING_TOGGLE);
44 113
     },
45 114
     "toolbar_button_security": function () {
46
-        if (sharedKey) {
47
-            AnalyticsAdapter.sendEvent('toolbar.lock.disabled');
48
-        } else {
49
-            AnalyticsAdapter.sendEvent('toolbar.lock.enabled');
50
-        }
51
-        return Toolbar.openLockDialog();
115
+        emitter.emit(UIEvents.ROOM_LOCK_CLICKED);
52 116
     },
53 117
     "toolbar_button_link": function () {
54 118
         AnalyticsAdapter.sendEvent('toolbar.invite.clicked');
55
-        return Toolbar.openLinkDialog();
119
+        openLinkDialog();
56 120
     },
57 121
     "toolbar_button_chat": function () {
58 122
         AnalyticsAdapter.sendEvent('toolbar.chat.toggled');
59
-        return BottomToolbar.toggleChat();
123
+        emitter.emit(UIEvents.TOGGLE_CHAT);
60 124
     },
61 125
     "toolbar_button_prezi": function () {
62 126
         AnalyticsAdapter.sendEvent('toolbar.prezi.clicked');
63
-        return Prezi.openPreziDialog();
127
+        emitter.emit(UIEvents.PREZI_CLICKED);
64 128
     },
65 129
     "toolbar_button_etherpad": function () {
66 130
         AnalyticsAdapter.sendEvent('toolbar.etherpad.clicked');
67
-        return Etherpad.toggleEtherpad(0);
131
+        emitter.emit(UIEvents.ETHERPAD_CLICKED);
68 132
     },
69 133
     "toolbar_button_desktopsharing": function () {
70 134
         if (APP.desktopsharing.isUsingScreenStream) {
@@ -72,32 +136,32 @@ var buttonHandlers = {
72 136
         } else {
73 137
             AnalyticsAdapter.sendEvent('toolbar.screen.enabled');
74 138
         }
75
-        return APP.desktopsharing.toggleScreenSharing();
139
+        emitter.emit(UIEvents.TOGGLE_SCREENSHARING);
76 140
     },
77 141
     "toolbar_button_fullScreen": function() {
78 142
         AnalyticsAdapter.sendEvent('toolbar.fullscreen.enabled');
79 143
         UIUtil.buttonClick("#toolbar_button_fullScreen", "icon-full-screen icon-exit-full-screen");
80
-        return Toolbar.toggleFullScreen();
144
+        emitter.emit(UIEvents.FULLSCREEN_TOGGLE);
81 145
     },
82 146
     "toolbar_button_sip": function () {
83 147
         AnalyticsAdapter.sendEvent('toolbar.sip.clicked');
84
-        return callSipButtonClicked();
148
+        showSipNumberInput();
85 149
     },
86 150
     "toolbar_button_dialpad": function () {
87 151
         AnalyticsAdapter.sendEvent('toolbar.sip.dialpad.clicked');
88
-        return dialpadButtonClicked();
152
+        dialpadButtonClicked();
89 153
     },
90 154
     "toolbar_button_settings": function () {
91 155
         AnalyticsAdapter.sendEvent('toolbar.settings.toggled');
92
-        PanelToggler.toggleSettingsMenu();
156
+        emitter.emit(UIEvents.TOGGLE_SETTINGS);
93 157
     },
94 158
     "toolbar_button_hangup": function () {
95 159
         AnalyticsAdapter.sendEvent('toolbar.hangup');
96
-        return hangup();
160
+        emitter.emit(UIEvents.HANGUP);
97 161
     },
98 162
     "toolbar_button_login": function () {
99 163
         AnalyticsAdapter.sendEvent('toolbar.authenticate.login.clicked');
100
-        Toolbar.authenticateClicked();
164
+        emitter.emit(UIEvents.AUTH_CLICKED);
101 165
     },
102 166
     "toolbar_button_logout": function () {
103 167
         AnalyticsAdapter.sendEvent('toolbar.authenticate.logout.clicked');
@@ -111,631 +175,218 @@ var buttonHandlers = {
111 175
             "dialog.Yes",
112 176
             function (evt, yes) {
113 177
                 if (yes) {
114
-                    APP.xmpp.logout(function (url) {
115
-                        if (url) {
116
-                            window.location.href = url;
117
-                        } else {
118
-                            hangup();
119
-                        }
120
-                    });
178
+                    emitter.emit(UIEvents.LOGOUT);
121 179
                 }
122
-            });
180
+            }
181
+        );
123 182
     }
124 183
 };
125
-var defaultToolbarButtons = {
184
+const defaultToolbarButtons = {
126 185
     'microphone': '#toolbar_button_mute',
127
-    'camera': '#toolbar_button_camera',
128
-    'desktop': '#toolbar_button_desktopsharing',
129
-    'security': '#toolbar_button_security',
130
-    'invite': '#toolbar_button_link',
131
-    'chat': '#toolbar_button_chat',
132
-    'prezi': '#toolbar_button_prezi',
133
-    'etherpad': '#toolbar_button_etherpad',
186
+    'camera':     '#toolbar_button_camera',
187
+    'desktop':    '#toolbar_button_desktopsharing',
188
+    'security':   '#toolbar_button_security',
189
+    'invite':     '#toolbar_button_link',
190
+    'chat':       '#toolbar_button_chat',
191
+    'prezi':      '#toolbar_button_prezi',
192
+    'etherpad':   '#toolbar_button_etherpad',
134 193
     'fullscreen': '#toolbar_button_fullScreen',
135
-    'settings': '#toolbar_button_settings',
136
-    'hangup': '#toolbar_button_hangup'
194
+    'settings':   '#toolbar_button_settings',
195
+    'hangup':     '#toolbar_button_hangup'
137 196
 };
138 197
 
139
-/**
140
- * Hangs up this call.
141
- */
142
-function hangup() {
143
-    var conferenceDispose = function () {
144
-        APP.xmpp.disposeConference();
145
-
146
-        if (config.enableWelcomePage) {
147
-            setTimeout(function() {
148
-                window.localStorage.welcomePageDisabled = false;
149
-                window.location.pathname = "/";
150
-            }, 3000);
151
-        }
152
-    };
153
-
154
-    if (Feedback.isEnabled())
155
-    {
156
-        // If the user has already entered feedback, we'll show the window and
157
-        // immidiately start the conference dispose timeout.
158
-        if (Feedback.feedbackScore > 0) {
159
-            Feedback.openFeedbackWindow();
160
-            conferenceDispose();
161
-
162
-        }
163
-        // Otherwise we'll wait for user's feedback.
164
-        else
165
-            Feedback.openFeedbackWindow(conferenceDispose);
166
-    }
167
-    else {
168
-        conferenceDispose();
169
-
170
-        // If the feedback functionality isn't enabled we show a thank you
171
-        // dialog.
172
-        APP.UI.messageHandler.openMessageDialog(null, null, null,
173
-            APP.translation.translateString("dialog.thankYou",
174
-                {appName:interfaceConfig.APP_NAME}));
175
-    }
176
-}
177
-
178
-/**
179
- * Starts or stops the recording for the conference.
180
- */
181
-function toggleRecording(predefinedToken) {
182
-    APP.xmpp.toggleRecording(function (callback) {
183
-        if (predefinedToken) {
184
-            callback(UIUtil.escapeHtml(predefinedToken));
185
-            return;
186
-        }
187
-
188
-        var msg = APP.translation.generateTranslationHTML(
189
-            "dialog.recordingToken");
190
-        var token = APP.translation.translateString("dialog.token");
191
-        APP.UI.messageHandler.openTwoButtonDialog(null, null, null,
192
-                '<h2>' + msg + '</h2>' +
193
-                '<input name="recordingToken" type="text" ' +
194
-                ' data-i18n="[placeholder]dialog.token" ' +
195
-                'placeholder="' + token + '" autofocus>',
196
-            false,
197
-            "dialog.Save",
198
-            function (e, v, m, f) {
199
-                if (v) {
200
-                    var token = f.recordingToken;
201
-
202
-                    if (token) {
203
-                        callback(UIUtil.escapeHtml(token));
204
-                    }
205
-                }
206
-            },
207
-            null,
208
-            function () { },
209
-            ':input:first'
210
-        );
211
-    }, Toolbar.setRecordingButtonState);
212
-}
213
-
214
-/**
215
- * Locks / unlocks the room.
216
- */
217
-function lockRoom(lock) {
218
-    var currentSharedKey = '';
219
-    if (lock)
220
-        currentSharedKey = sharedKey;
221
-
222
-    APP.xmpp.lockRoom(currentSharedKey, function (res) {
223
-        // password is required
224
-        if (sharedKey) {
225
-            console.log('set room password');
226
-            Toolbar.lockLockButton();
227
-        }
228
-        else {
229
-            console.log('removed room password');
230
-            Toolbar.unlockLockButton();
231
-        }
232
-    }, function (err) {
233
-        console.warn('setting password failed', err);
234
-        messageHandler.showError("dialog.lockTitle",
235
-            "dialog.lockMessage");
236
-        Toolbar.setSharedKey('');
237
-    }, function () {
238
-        console.warn('room passwords not supported');
239
-        messageHandler.showError("dialog.warning",
240
-            "dialog.passwordNotSupported");
241
-        Toolbar.setSharedKey('');
242
-    });
243
-}
244
-
245
-/**
246
- * Invite participants to conference.
247
- */
248
-function inviteParticipants() {
249
-    if (roomUrl === null)
250
-        return;
251
-
252
-    var sharedKeyText = "";
253
-    if (sharedKey && sharedKey.length > 0) {
254
-        sharedKeyText =
255
-            APP.translation.translateString("email.sharedKey",
256
-                {sharedKey: sharedKey});
257
-        sharedKeyText = sharedKeyText.replace(/\n/g, "%0D%0A");
258
-    }
259
-
260
-    var supportedBrowsers = "Chromium, Google Chrome " +
261
-        APP.translation.translateString("email.and") + " Opera";
262
-    var conferenceName = roomUrl.substring(roomUrl.lastIndexOf('/') + 1);
263
-    var subject = APP.translation.translateString("email.subject",
264
-        {appName:interfaceConfig.APP_NAME, conferenceName: conferenceName});
265
-    var body = APP.translation.translateString("email.body",
266
-        {appName:interfaceConfig.APP_NAME, sharedKeyText: sharedKeyText,
267
-            roomUrl: roomUrl, supportedBrowsers: supportedBrowsers});
268
-    body = body.replace(/\n/g, "%0D%0A");
269
-
270
-    if (window.localStorage.displayname) {
271
-        body += "%0D%0A%0D%0A" + window.localStorage.displayname;
272
-    }
273
-
274
-    if (interfaceConfig.INVITATION_POWERED_BY) {
275
-        body += "%0D%0A%0D%0A--%0D%0Apowered by jitsi.org";
276
-    }
277
-
278
-    window.open("mailto:?subject=" + subject + "&body=" + body, '_blank');
279
-}
280
-
281 198
 function dialpadButtonClicked() {
282 199
     //TODO show the dialpad box
283 200
 }
284 201
 
285
-function callSipButtonClicked() {
286
-    var defaultNumber
287
-        = config.defaultSipNumber ? config.defaultSipNumber : '';
288
-
289
-    var sipMsg = APP.translation.generateTranslationHTML(
290
-        "dialog.sipMsg");
291
-    messageHandler.openTwoButtonDialog(null, null, null,
292
-        '<h2>' + sipMsg + '</h2>' +
293
-        '<input name="sipNumber" type="text"' +
294
-        ' value="' + defaultNumber + '" autofocus>',
295
-        false,
296
-        "dialog.Dial",
202
+function showSipNumberInput () {
203
+    let defaultNumber = config.defaultSipNumber
204
+        ? config.defaultSipNumber
205
+        : '';
206
+
207
+    let sipMsg = APP.translation.generateTranslationHTML("dialog.sipMsg");
208
+    messageHandler.openTwoButtonDialog(
209
+        null, null, null,
210
+        `<h2>${sipMsg}</h2>
211
+            <input name="sipNumber" type="text" value="${defaultNumber}" autofocus>`,
212
+        false, "dialog.Dial",
297 213
         function (e, v, m, f) {
298
-            if (v) {
299
-                var numberInput = f.sipNumber;
300
-                if (numberInput) {
301
-                    APP.xmpp.dial(
302
-                        numberInput, 'fromnumber', UI.getRoomName(), sharedKey);
303
-                }
214
+            if (v && f.sipNumber) {
215
+                emitter.emit(UIEvents.SIP_DIAL, f.sipNumber);
304 216
             }
305 217
         },
306 218
         null, null, ':input:first'
307 219
     );
308 220
 }
309 221
 
310
-var Toolbar = (function (my) {
222
+const Toolbar = {
223
+    init (eventEmitter) {
224
+        emitter = eventEmitter;
311 225
 
312
-    my.init = function (ui) {
313 226
         UIUtil.hideDisabledButtons(defaultToolbarButtons);
314 227
 
315
-        for(var k in buttonHandlers)
316
-            $("#" + k).click(buttonHandlers[k]);
317
-        UI = ui;
318
-        // Update login info
319
-        APP.xmpp.addListener(
320
-            AuthenticationEvents.IDENTITY_UPDATED,
321
-            function (authenticationEnabled, userIdentity) {
322
-
323
-                var loggedIn = false;
324
-                if (userIdentity) {
325
-                    loggedIn = true;
326
-                }
327
-
328
-                Toolbar.showAuthenticateButton(authenticationEnabled);
329
-
330
-                if (authenticationEnabled) {
331
-                    Toolbar.setAuthenticatedIdentity(userIdentity);
332
-
333
-                    Toolbar.showLoginButton(!loggedIn);
334
-                    Toolbar.showLogoutButton(loggedIn);
335
-                }
336
-            }
228
+        Object.keys(buttonHandlers).forEach(
229
+            buttonId => $(`#${buttonId}`).click(buttonHandlers[buttonId])
337 230
         );
338
-    };
339
-
340
-    /**
341
-     * Sets shared key
342
-     * @param sKey the shared key
343
-     */
344
-    my.setSharedKey = function (sKey) {
345
-        sharedKey = sKey;
346
-    };
347
-
348
-    my.authenticateClicked = function () {
349
-        Authentication.focusAuthenticationWindow();
350
-        if (!APP.xmpp.isExternalAuthEnabled()) {
351
-            Authentication.xmppAuthenticate();
352
-            return;
353
-        }
354
-        // Get authentication URL
355
-        if (!APP.xmpp.isMUCJoined()) {
356
-            APP.xmpp.getLoginUrl(UI.getRoomName(), function (url) {
357
-                // If conference has not been started yet - redirect to login page
358
-                window.location.href = url;
359
-            });
360
-        } else {
361
-            APP.xmpp.getPopupLoginUrl(UI.getRoomName(), function (url) {
362
-                // Otherwise - open popup with authentication URL
363
-                var authenticationWindow = Authentication.createAuthenticationWindow(
364
-                    function () {
365
-                        // On popup closed - retry room allocation
366
-                        APP.xmpp.allocateConferenceFocus(
367
-                            APP.UI.getRoomName(),
368
-                            function () { console.info("AUTH DONE"); }
369
-                        );
370
-                    }, url);
371
-                if (!authenticationWindow) {
372
-                    messageHandler.openMessageDialog(
373
-                        null, "dialog.popupError");
374
-                }
375
-            });
376
-        }
377
-    };
231
+    },
378 232
 
379 233
     /**
380 234
      * Updates the room invite url.
381 235
      */
382
-    my.updateRoomUrl = function (newRoomUrl) {
236
+    updateRoomUrl (newRoomUrl) {
383 237
         roomUrl = newRoomUrl;
384 238
 
385 239
         // If the invite dialog has been already opened we update the information.
386
-        var inviteLink = document.getElementById('inviteLinkRef');
240
+        let inviteLink = document.getElementById('inviteLinkRef');
387 241
         if (inviteLink) {
388 242
             inviteLink.value = roomUrl;
389 243
             inviteLink.select();
390 244
             $('#inviteLinkRef').parent()
391 245
                 .find('button[value=true]').prop('disabled', false);
392 246
         }
393
-    };
247
+    },
394 248
 
395 249
     /**
396 250
      * Disables and enables some of the buttons.
397 251
      */
398
-    my.setupButtonsFromConfig = function () {
252
+    setupButtonsFromConfig () {
399 253
         if (!UIUtil.isButtonEnabled('prezi')) {
400 254
             $("#toolbar_button_prezi").css({display: "none"});
401 255
         }
402
-    };
403
-
404
-    /**
405
-     * Opens the lock room dialog.
406
-     */
407
-    my.openLockDialog = function () {
408
-        // Only the focus is able to set a shared key.
409
-        if (!APP.xmpp.isModerator()) {
410
-            if (sharedKey) {
411
-                messageHandler.openMessageDialog(null,
412
-                    "dialog.passwordError");
413
-            } else {
414
-                messageHandler.openMessageDialog(null, "dialog.passwordError2");
415
-            }
416
-        } else {
417
-            if (sharedKey) {
418
-                messageHandler.openTwoButtonDialog(null, null,
419
-                    "dialog.passwordCheck",
420
-                    null,
421
-                    false,
422
-                    "dialog.Remove",
423
-                    function (e, v) {
424
-                        if (v) {
425
-                            Toolbar.setSharedKey('');
426
-                            lockRoom(false);
427
-                        }
428
-                    });
429
-            } else {
430
-                var msg = APP.translation.generateTranslationHTML(
431
-                    "dialog.passwordMsg");
432
-                var yourPassword = APP.translation.translateString(
433
-                    "dialog.yourPassword");
434
-                messageHandler.openTwoButtonDialog(null, null, null,
435
-                    '<h2>' + msg + '</h2>' +
436
-                        '<input name="lockKey" type="text"' +
437
-                        ' data-i18n="[placeholder]dialog.yourPassword" ' +
438
-                        'placeholder="' + yourPassword + '" autofocus>',
439
-                    false,
440
-                    "dialog.Save",
441
-                    function (e, v, m, f) {
442
-                        if (v) {
443
-                            var lockKey = f.lockKey;
444
-
445
-                            if (lockKey) {
446
-                                Toolbar.setSharedKey(
447
-                                    UIUtil.escapeHtml(lockKey));
448
-                                lockRoom(true);
449
-                            }
450
-                        }
451
-                    },
452
-                    null, null, 'input:first'
453
-                );
454
-            }
455
-        }
456
-    };
457
-
458
-    /**
459
-     * Opens the invite link dialog.
460
-     */
461
-    my.openLinkDialog = function () {
462
-        var inviteAttributes;
463
-
464
-        if (roomUrl === null) {
465
-            inviteAttributes = 'data-i18n="[value]roomUrlDefaultMsg" value="' +
466
-            APP.translation.translateString("roomUrlDefaultMsg") + '"';
467
-        } else {
468
-            inviteAttributes = "value=\"" + encodeURI(roomUrl) + "\"";
469
-        }
470
-        messageHandler.openTwoButtonDialog("dialog.shareLink",
471
-            null, null,
472
-            '<input id="inviteLinkRef" type="text" ' +
473
-                inviteAttributes + ' onclick="this.select();" readonly>',
474
-            false,
475
-            "dialog.Invite",
476
-            function (e, v) {
477
-                if (v) {
478
-                    if (roomUrl) {
479
-                        inviteParticipants();
480
-                    }
481
-                }
482
-            },
483
-            function (event) {
484
-                if (roomUrl) {
485
-                    document.getElementById('inviteLinkRef').select();
486
-                } else {
487
-                    if (event && event.target)
488
-                        $(event.target)
489
-                            .find('button[value=true]').prop('disabled', true);
490
-                }
491
-            }
492
-        );
493
-    };
494
-
495
-    /**
496
-     * Opens the settings dialog.
497
-     * FIXME: not used ?
498
-     */
499
-    my.openSettingsDialog = function () {
500
-        var settings1 = APP.translation.generateTranslationHTML(
501
-            "dialog.settings1");
502
-        var settings2 = APP.translation.generateTranslationHTML(
503
-            "dialog.settings2");
504
-        var settings3 = APP.translation.generateTranslationHTML(
505
-            "dialog.settings3");
506
-
507
-        var yourPassword = APP.translation.translateString(
508
-            "dialog.yourPassword");
509
-
510
-        messageHandler.openTwoButtonDialog(null,
511
-            '<h2>' + settings1 + '</h2>' +
512
-                '<input type="checkbox" id="initMuted">' +
513
-                settings2 + '<br/>' +
514
-                '<input type="checkbox" id="requireNicknames">' +
515
-                 settings3 +
516
-                '<input id="lockKey" type="text" placeholder="' + yourPassword +
517
-                '" data-i18n="[placeholder]dialog.yourPassword" autofocus>',
518
-            null,
519
-            null,
520
-            false,
521
-            "dialog.Save",
522
-            function () {
523
-                document.getElementById('lockKey').focus();
524
-            },
525
-            function (e, v) {
526
-                if (v) {
527
-                    if ($('#initMuted').is(":checked")) {
528
-                        // it is checked
529
-                    }
530
-
531
-                    if ($('#requireNicknames').is(":checked")) {
532
-                        // it is checked
533
-                    }
534
-                    /*
535
-                    var lockKey = document.getElementById('lockKey');
536
-
537
-                    if (lockKey.value) {
538
-                        setSharedKey(lockKey.value);
539
-                        lockRoom(true);
540
-                    }
541
-                    */
542
-                }
543
-            }
544
-        );
545
-    };
256
+    },
546 257
 
547
-    /**
548
-     * Toggles the application in and out of full screen mode
549
-     * (a.k.a. presentation mode in Chrome).
550
-     */
551
-    my.toggleFullScreen = function () {
552
-        var fsElement = document.documentElement;
553
-
554
-        if (!document.mozFullScreen && !document.webkitIsFullScreen) {
555
-            //Enter Full Screen
556
-            if (fsElement.mozRequestFullScreen) {
557
-                fsElement.mozRequestFullScreen();
558
-            }
559
-            else {
560
-                fsElement.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT);
561
-            }
562
-        } else {
563
-            //Exit Full Screen
564
-            if (document.mozCancelFullScreen) {
565
-                document.mozCancelFullScreen();
566
-            } else {
567
-                document.webkitCancelFullScreen();
568
-            }
569
-        }
570
-    };
571 258
     /**
572 259
      * Unlocks the lock button state.
573 260
      */
574
-    my.unlockLockButton = function () {
261
+    unlockLockButton () {
575 262
         if ($("#toolbar_button_security").hasClass("icon-security-locked"))
576 263
             UIUtil.buttonClick("#toolbar_button_security", "icon-security icon-security-locked");
577
-    };
264
+    },
265
+
578 266
     /**
579 267
      * Updates the lock button state to locked.
580 268
      */
581
-    my.lockLockButton = function () {
269
+    lockLockButton () {
582 270
         if ($("#toolbar_button_security").hasClass("icon-security"))
583 271
             UIUtil.buttonClick("#toolbar_button_security", "icon-security icon-security-locked");
584
-    };
272
+    },
585 273
 
586 274
     /**
587 275
      * Shows or hides authentication button
588 276
      * @param show <tt>true</tt> to show or <tt>false</tt> to hide
589 277
      */
590
-    my.showAuthenticateButton = function (show) {
278
+    showAuthenticateButton (show) {
591 279
         if (UIUtil.isButtonEnabled('authentication') && show) {
592 280
             $('#authentication').css({display: "inline"});
593
-        }
594
-        else {
281
+        } else {
595 282
             $('#authentication').css({display: "none"});
596 283
         }
597
-    };
284
+    },
285
+
286
+    showEtherpadButton () {
287
+        if (!$('#toolbar_button_etherpad').is(":visible")) {
288
+            $('#toolbar_button_etherpad').css({display: 'inline-block'});
289
+        }
290
+    },
598 291
 
599 292
     // Shows or hides the 'recording' button.
600
-    my.showRecordingButton = function (show) {
293
+    showRecordingButton (show) {
601 294
         if (UIUtil.isButtonEnabled('recording') && show) {
602 295
             $('#toolbar_button_record').css({display: "inline-block"});
603
-        }
604
-        else {
296
+        } else {
605 297
             $('#toolbar_button_record').css({display: "none"});
606 298
         }
607
-    };
608
-
609
-    // Sets the state of the recording button
610
-    my.setRecordingButtonState = function (recordingState) {
611
-        var selector = $('#toolbar_button_record');
612
-
613
-        if (recordingState === 'on') {
614
-            selector.removeClass("icon-recEnable");
615
-            selector.addClass("icon-recEnable active");
616
-
617
-            $("#largeVideo").toggleClass("videoMessageFilter", true);
618
-            var recordOnKey = "recording.on";
619
-            $('#videoConnectionMessage').attr("data-i18n", recordOnKey);
620
-            $('#videoConnectionMessage').text(APP.translation.translateString(recordOnKey));
621
-
622
-            setTimeout(function(){
623
-                $("#largeVideo").toggleClass("videoMessageFilter", false);
624
-                $('#videoConnectionMessage').css({display: "none"});
625
-            }, 1500);
626
-
627
-            recordingToaster = messageHandler.notify(null, "recording.toaster", null,
628
-                null, null, {timeOut: 0, closeButton: null, tapToDismiss: false});
629
-        } else if (recordingState === 'off') {
630
-            selector.removeClass("icon-recEnable active");
631
-            selector.addClass("icon-recEnable");
632
-
633
-            $("#largeVideo").toggleClass("videoMessageFilter", false);
634
-            $('#videoConnectionMessage').css({display: "none"});
635
-
636
-            if (recordingToaster)
637
-                messageHandler.remove(recordingToaster);
638
-
639
-        } else if (recordingState === 'pending') {
640
-            selector.removeClass("icon-recEnable active");
641
-            selector.addClass("icon-recEnable");
642
-
643
-            $("#largeVideo").toggleClass("videoMessageFilter", true);
644
-            var recordPendingKey = "recording.pending";
645
-            $('#videoConnectionMessage').attr("data-i18n", recordPendingKey);
646
-            $('#videoConnectionMessage').text(APP.translation.translateString(recordPendingKey));
647
-            $('#videoConnectionMessage').css({display: "block"});
648
-        }
649
-    };
299
+    },
650 300
 
651 301
     // checks whether recording is enabled and whether we have params
652 302
     // to start automatically recording
653
-    my.checkAutoRecord = function () {
303
+    checkAutoRecord () {
654 304
         if (UIUtil.isButtonEnabled('recording') && config.autoRecord) {
655
-            toggleRecording(config.autoRecordToken);
305
+            emitter.emit(UIEvents.RECORDING_TOGGLE, UIUtil.escapeHtml(config.autoRecordToken));
656 306
         }
657
-    };
307
+    },
658 308
 
659 309
     // checks whether desktop sharing is enabled and whether
660 310
     // we have params to start automatically sharing
661
-    my.checkAutoEnableDesktopSharing = function () {
662
-        if (UIUtil.isButtonEnabled('desktop')
663
-                && config.autoEnableDesktopSharing) {
664
-            APP.desktopsharing.toggleScreenSharing();
311
+    checkAutoEnableDesktopSharing () {
312
+        if (UIUtil.isButtonEnabled('desktop') && config.autoEnableDesktopSharing) {
313
+            emitter.emit(UIEvents.TOGGLE_SCREENSHARING);
665 314
         }
666
-    };
315
+    },
667 316
 
668 317
     // Shows or hides SIP calls button
669
-    my.showSipCallButton = function (show) {
670
-        if (APP.xmpp.isSipGatewayEnabled() && UIUtil.isButtonEnabled('sip') && show) {
318
+    showSipCallButton (show) {
319
+        if (APP.conference.sipGatewayEnabled() && UIUtil.isButtonEnabled('sip') && show) {
671 320
             $('#toolbar_button_sip').css({display: "inline-block"});
672 321
         } else {
673 322
             $('#toolbar_button_sip').css({display: "none"});
674 323
         }
675
-    };
324
+    },
676 325
 
677 326
     // Shows or hides the dialpad button
678
-    my.showDialPadButton = function (show) {
327
+    showDialPadButton (show) {
679 328
         if (UIUtil.isButtonEnabled('dialpad') && show) {
680 329
             $('#toolbar_button_dialpad').css({display: "inline-block"});
681 330
         } else {
682 331
             $('#toolbar_button_dialpad').css({display: "none"});
683 332
         }
684
-    };
333
+    },
685 334
 
686 335
     /**
687 336
      * Displays user authenticated identity name(login).
688 337
      * @param authIdentity identity name to be displayed.
689 338
      */
690
-    my.setAuthenticatedIdentity = function (authIdentity) {
339
+    setAuthenticatedIdentity (authIdentity) {
691 340
         if (authIdentity) {
692
-            var selector = $('#toolbar_auth_identity');
341
+            let selector = $('#toolbar_auth_identity');
693 342
             selector.css({display: "list-item"});
694 343
             selector.text(authIdentity);
695 344
         } else {
696 345
             $('#toolbar_auth_identity').css({display: "none"});
697 346
         }
698
-    };
347
+    },
699 348
 
700 349
     /**
701 350
      * Shows/hides login button.
702 351
      * @param show <tt>true</tt> to show
703 352
      */
704
-    my.showLoginButton = function (show) {
353
+    showLoginButton (show) {
705 354
         if (UIUtil.isButtonEnabled('authentication') && show) {
706 355
             $('#toolbar_button_login').css({display: "list-item"});
707 356
         } else {
708 357
             $('#toolbar_button_login').css({display: "none"});
709 358
         }
710
-    };
359
+    },
711 360
 
712 361
     /**
713 362
      * Shows/hides logout button.
714 363
      * @param show <tt>true</tt> to show
715 364
      */
716
-    my.showLogoutButton = function (show) {
365
+    showLogoutButton (show) {
717 366
         if (UIUtil.isButtonEnabled('authentication') && show) {
718 367
             $('#toolbar_button_logout').css({display: "list-item"});
719 368
         } else {
720 369
             $('#toolbar_button_logout').css({display: "none"});
721 370
         }
722
-    };
371
+    },
723 372
 
724 373
     /**
725 374
      * Sets the state of the button. The button has blue glow if desktop
726 375
      * streaming is active.
727 376
      * @param active the state of the desktop streaming.
728 377
      */
729
-    my.changeDesktopSharingButtonState = function (active) {
730
-        var button = $("#toolbar_button_desktopsharing");
378
+    changeDesktopSharingButtonState (active) {
379
+        let button = $("#toolbar_button_desktopsharing");
731 380
         if (active) {
732 381
             button.addClass("glow");
733 382
         } else {
734 383
             button.removeClass("glow");
735 384
         }
736
-    };
385
+    },
737 386
 
738
-    return my;
739
-}(Toolbar || {}));
387
+    updateRecordingState (state) {
388
+        setRecordingButtonState(state);
389
+    }
390
+};
740 391
 
741
-module.exports = Toolbar;
392
+export default Toolbar;

+ 43
- 47
modules/UI/toolbars/ToolbarToggler.js 查看文件

@@ -1,9 +1,10 @@
1
-/* global APP, config, $, interfaceConfig, Moderator,
2
- DesktopStreaming.showDesktopSharingButton */
1
+/* global APP, config, $, interfaceConfig */
3 2
 
4
-var toolbarTimeoutObject,
5
-    toolbarTimeout = interfaceConfig.INITIAL_TOOLBAR_TIMEOUT,
6
-    UIUtil = require("../util/UIUtil");
3
+import UIUtil from '../util/UIUtil';
4
+import BottomToolbar from './BottomToolbar';
5
+
6
+let toolbarTimeoutObject;
7
+let toolbarTimeout = interfaceConfig.INITIAL_TOOLBAR_TIMEOUT;
7 8
 
8 9
 function showDesktopSharingButton() {
9 10
     if (APP.desktopsharing.isDesktopSharingEnabled() &&
@@ -14,19 +15,24 @@ function showDesktopSharingButton() {
14 15
     }
15 16
 }
16 17
 
18
+function isToolbarVisible () {
19
+    return $('#header').is(':visible');
20
+}
21
+
17 22
 /**
18 23
  * Hides the toolbar.
19 24
  */
20 25
 function hideToolbar() {
21
-    if(config.alwaysVisibleToolbar)
26
+    if (config.alwaysVisibleToolbar) {
22 27
         return;
28
+    }
23 29
 
24
-    var header = $("#header"),
25
-        bottomToolbar = $("#bottomToolbar");
26
-    var isToolbarHover = false;
30
+    let header = $("#header");
31
+    let bottomToolbar = $("#bottomToolbar");
32
+    let isToolbarHover = false;
27 33
     header.find('*').each(function () {
28
-        var id = $(this).attr('id');
29
-        if ($("#" + id + ":hover").length > 0) {
34
+        let id = $(this).attr('id');
35
+        if ($(`#${id}:hover`).length > 0) {
30 36
             isToolbarHover = true;
31 37
         }
32 38
     });
@@ -37,34 +43,36 @@ function hideToolbar() {
37 43
     clearTimeout(toolbarTimeoutObject);
38 44
     toolbarTimeoutObject = null;
39 45
 
40
-    if (!isToolbarHover) {
46
+    if (isToolbarHover) {
47
+        toolbarTimeoutObject = setTimeout(hideToolbar, toolbarTimeout);
48
+    } else {
41 49
         header.hide("slide", { direction: "up", duration: 300});
42 50
         $('#subject').animate({top: "-=40"}, 300);
43
-        if ($("#remoteVideos").hasClass("hidden")) {
51
+        if (!BottomToolbar.isFilmStripVisible()) {
44 52
             bottomToolbar.hide(
45
-                "slide", {direction: "right", duration: 300});
53
+                "slide", {direction: "right", duration: 300}
54
+            );
46 55
         }
47 56
     }
48
-    else {
49
-        toolbarTimeoutObject = setTimeout(hideToolbar, toolbarTimeout);
50
-    }
51 57
 }
52 58
 
53
-var ToolbarToggler = {
59
+const ToolbarToggler = {
54 60
     /**
55 61
      * Shows the main toolbar.
56 62
      */
57
-    showToolbar: function () {
58
-        if (interfaceConfig.filmStripOnly)
63
+    showToolbar () {
64
+        if (interfaceConfig.filmStripOnly) {
59 65
             return;
60
-        var header = $("#header"),
61
-            bottomToolbar = $("#bottomToolbar");
66
+        }
67
+        let header = $("#header");
68
+        let bottomToolbar = $("#bottomToolbar");
62 69
         if (!header.is(':visible') || !bottomToolbar.is(":visible")) {
63 70
             header.show("slide", { direction: "up", duration: 300});
64 71
             $('#subject').animate({top: "+=40"}, 300);
65 72
             if (!bottomToolbar.is(":visible")) {
66 73
                 bottomToolbar.show(
67
-                    "slide", {direction: "right", duration: 300});
74
+                    "slide", {direction: "right", duration: 300}
75
+                );
68 76
             }
69 77
 
70 78
             if (toolbarTimeoutObject) {
@@ -75,13 +83,6 @@ var ToolbarToggler = {
75 83
             toolbarTimeout = interfaceConfig.TOOLBAR_TIMEOUT;
76 84
         }
77 85
 
78
-        if (APP.xmpp.isModerator())
79
-        {
80
-//            TODO: Enable settings functionality.
81
-//                  Need to uncomment the settings button in index.html.
82
-//            $('#settingsButton').css({visibility:"visible"});
83
-        }
84
-
85 86
         // Show/hide desktop sharing button
86 87
         showDesktopSharingButton();
87 88
     },
@@ -91,33 +92,28 @@ var ToolbarToggler = {
91 92
      *
92 93
      * @param isDock indicates what operation to perform
93 94
      */
94
-    dockToolbar: function (isDock) {
95
-        if (interfaceConfig.filmStripOnly)
95
+    dockToolbar (isDock) {
96
+        if (interfaceConfig.filmStripOnly) {
96 97
             return;
98
+        }
97 99
 
98 100
         if (isDock) {
99 101
             // First make sure the toolbar is shown.
100
-            if (!$('#header').is(':visible')) {
102
+            if (!isToolbarVisible()) {
101 103
                 this.showToolbar();
102 104
             }
103 105
 
104 106
             // Then clear the time out, to dock the toolbar.
105
-            if (toolbarTimeoutObject) {
106
-                clearTimeout(toolbarTimeoutObject);
107
-                toolbarTimeoutObject = null;
108
-            }
109
-        }
110
-        else {
111
-            if (!$('#header').is(':visible')) {
112
-                this.showToolbar();
113
-            }
114
-            else {
107
+            clearTimeout(toolbarTimeoutObject);
108
+            toolbarTimeoutObject = null;
109
+        } else {
110
+            if (isToolbarVisible()) {
115 111
                 toolbarTimeoutObject = setTimeout(hideToolbar, toolbarTimeout);
112
+            } else {
113
+                this.showToolbar();
116 114
             }
117 115
         }
118
-    },
119
-
120
-    showDesktopSharingButton: showDesktopSharingButton
116
+    }
121 117
 };
122 118
 
123
-module.exports = ToolbarToggler;
119
+module.exports = ToolbarToggler;

+ 0
- 30
modules/UI/util/NicknameHandler.js 查看文件

@@ -1,30 +0,0 @@
1
-var UIEvents = require("../../../service/UI/UIEvents");
2
-
3
-var nickname = null;
4
-var eventEmitter = null;
5
-
6
-var NicknameHandler = {
7
-    init: function (emitter) {
8
-        eventEmitter = emitter;
9
-        var storedDisplayName = window.localStorage.displayname;
10
-        if (storedDisplayName) {
11
-            nickname = storedDisplayName;
12
-        }
13
-    },
14
-    setNickname: function (newNickname) {
15
-        if (!newNickname || nickname === newNickname)
16
-            return;
17
-
18
-        nickname = newNickname;
19
-        window.localStorage.displayname = nickname;
20
-        eventEmitter.emit(UIEvents.NICKNAME_CHANGED, newNickname);
21
-    },
22
-    getNickname: function () {
23
-        return nickname;
24
-    },
25
-    addListener: function (type, listener) {
26
-        eventEmitter.on(type, listener);
27
-    }
28
-};
29
-
30
-module.exports = NicknameHandler;

+ 38
- 9
modules/UI/util/UIUtil.js 查看文件

@@ -1,17 +1,34 @@
1 1
 /* global $, config, interfaceConfig */
2
+
2 3
 /**
3 4
  * Created by hristo on 12/22/14.
4 5
  */
5
-var UIUtil = module.exports = {
6
+ var UIUtil = {
7
+
8
+    /**
9
+     * Returns the size of the side panel.
10
+     */
11
+     getSidePanelSize () {
12
+        var availableHeight = window.innerHeight;
13
+        var availableWidth = window.innerWidth;
14
+
15
+        var panelWidth = 200;
16
+        if (availableWidth * 0.2 < 200) {
17
+            panelWidth = availableWidth * 0.2;
18
+        }
19
+
20
+        return [panelWidth, availableHeight];
21
+     },
22
+
6 23
     /**
7 24
      * Returns the available video width.
8 25
      */
9
-    getAvailableVideoWidth: function (isVisible) {
10
-        var PanelToggler = require("../side_pannels/SidePanelToggler");
11
-        if(typeof isVisible === "undefined" || isVisible === null)
12
-            isVisible = PanelToggler.isVisible();
13
-        var rightPanelWidth
14
-            = isVisible ? PanelToggler.getPanelSize()[0] : 0;
26
+    getAvailableVideoWidth: function (isSidePanelVisible) {
27
+        let rightPanelWidth = 0;
28
+
29
+        if (isSidePanelVisible) {
30
+            rightPanelWidth = UIUtil.getSidePanelSize()[0];
31
+        }
15 32
 
16 33
         return window.innerWidth - rightPanelWidth;
17 34
     },
@@ -112,5 +129,17 @@ var UIUtil = module.exports = {
112 129
           .filter(function (item) { return item; })
113 130
           .join(',');
114 131
         $(selector).hide();
115
-    }
116
-};
132
+    },
133
+
134
+    redirect (url) {
135
+         window.location.href = url;
136
+     },
137
+
138
+     isFullScreen () {
139
+         return document.fullScreen
140
+             || document.mozFullScreen
141
+             || document.webkitIsFullScreen;
142
+     }
143
+};
144
+
145
+export default UIUtil;

+ 10
- 9
modules/UI/videolayout/ConnectionIndicator.js 查看文件

@@ -1,13 +1,13 @@
1 1
 /* global APP, $ */
2 2
 /* jshint -W101 */
3
-var JitsiPopover = require("../util/JitsiPopover");
3
+import JitsiPopover from "../util/JitsiPopover";
4 4
 
5 5
 /**
6 6
  * Constructs new connection indicator.
7 7
  * @param videoContainer the video container associated with the indicator.
8 8
  * @constructor
9 9
  */
10
-function ConnectionIndicator(videoContainer, jid) {
10
+function ConnectionIndicator(videoContainer, id) {
11 11
     this.videoContainer = videoContainer;
12 12
     this.bandwidth = null;
13 13
     this.packetLoss = null;
@@ -16,7 +16,7 @@ function ConnectionIndicator(videoContainer, jid) {
16 16
     this.resolution = null;
17 17
     this.transport = [];
18 18
     this.popover = null;
19
-    this.jid = jid;
19
+    this.id = id;
20 20
     this.create();
21 21
 }
22 22
 
@@ -87,7 +87,7 @@ ConnectionIndicator.prototype.generateText = function () {
87 87
     }
88 88
 
89 89
     var resolutionValue = null;
90
-    if(this.resolution && this.jid) {
90
+    if(this.resolution && this.id) {
91 91
         var keys = Object.keys(this.resolution);
92 92
         for(var ssrc in this.resolution) {
93 93
             // skip resolutions for ssrc that don't have this info
@@ -99,7 +99,7 @@ ConnectionIndicator.prototype.generateText = function () {
99 99
         }
100 100
     }
101 101
 
102
-    if(this.jid === null) {
102
+    if(this.id === null) {
103 103
         resolution = "";
104 104
         if(this.resolution === null || !Object.keys(this.resolution) ||
105 105
             Object.keys(this.resolution).length === 0) {
@@ -144,8 +144,8 @@ ConnectionIndicator.prototype.generateText = function () {
144 144
     if(this.videoContainer.videoSpanId == "localVideoContainer") {
145 145
         result += "<div class=\"jitsipopover_showmore\" " +
146 146
             "onclick = \"APP.UI.connectionIndicatorShowMore('" +
147
-            // FIXME: we do not know local jid when this text is generated
148
-            //this.jid + "')\"  data-i18n='connectionindicator." +
147
+            // FIXME: we do not know local id when this text is generated
148
+            //this.id + "')\"  data-i18n='connectionindicator." +
149 149
             "local')\"  data-i18n='connectionindicator." +
150 150
                 (this.showMoreValue ? "less" : "more") + "'>" +
151 151
             translate("connectionindicator." + (this.showMoreValue ? "less" : "more")) +
@@ -365,7 +365,8 @@ ConnectionIndicator.prototype.updateResolution = function (resolution) {
365 365
  */
366 366
 ConnectionIndicator.prototype.updatePopoverData = function () {
367 367
     this.popover.updateContent(
368
-        "<div class=\"connection_info\">" + this.generateText() + "</div>");
368
+        `<div class="connection_info">${this.generateText()}</div>`
369
+    );
369 370
     APP.translation.translateElement($(".connection_info"));
370 371
 };
371 372
 
@@ -385,4 +386,4 @@ ConnectionIndicator.prototype.hideIndicator = function () {
385 386
         this.popover.forceHide();
386 387
 };
387 388
 
388
-module.exports = ConnectionIndicator;
389
+export default ConnectionIndicator;

+ 41
- 0
modules/UI/videolayout/LargeContainer.js 查看文件

@@ -0,0 +1,41 @@
1
+
2
+/**
3
+ * Base class for all Large containers which we can show.
4
+ */
5
+export default class LargeContainer {
6
+
7
+    /**
8
+     * Show this container.
9
+     * @returns Promise
10
+     */
11
+    show () {
12
+    }
13
+
14
+    /**
15
+     * Hide this container.
16
+     * @returns Promise
17
+     */
18
+    hide () {
19
+    }
20
+
21
+    /**
22
+     * Resize this container.
23
+     * @param {number} containerWidth available width
24
+     * @param {number} containerHeight available height
25
+     * @param {boolean} animate if container should animate it's resize process
26
+     */
27
+    resize (containerWidth, containerHeight, animate) {
28
+    }
29
+
30
+    /**
31
+     * Handler for "hover in" events.
32
+     */
33
+    onHoverIn (e) {
34
+    }
35
+
36
+    /**
37
+     * Handler for "hover out" events.
38
+     */
39
+    onHoverOut (e) {
40
+    }
41
+}

+ 441
- 577
modules/UI/videolayout/LargeVideo.js
文件差异内容过多而无法显示
查看文件


+ 33
- 62
modules/UI/videolayout/LocalVideo.js 查看文件

@@ -1,19 +1,23 @@
1 1
 /* global $, interfaceConfig, APP */
2
-var SmallVideo = require("./SmallVideo");
3
-var ConnectionIndicator = require("./ConnectionIndicator");
4
-var NicknameHandler = require("../util/NicknameHandler");
5
-var UIUtil = require("../util/UIUtil");
2
+import ConnectionIndicator from "./ConnectionIndicator";
3
+import UIUtil from "../util/UIUtil";
4
+import UIEvents from "../../../service/UI/UIEvents";
5
+import SmallVideo from "./SmallVideo";
6
+
6 7
 var LargeVideo = require("./LargeVideo");
7 8
 var RTCBrowserType = require("../../RTC/RTCBrowserType");
8 9
 
9
-function LocalVideo(VideoLayout) {
10
+const TrackEvents = JitsiMeetJS.events.track;
11
+
12
+function LocalVideo(VideoLayout, emitter) {
10 13
     this.videoSpanId = "localVideoContainer";
11 14
     this.container = $("#localVideoContainer").get(0);
12 15
     this.bindHoverHandler();
13 16
     this.VideoLayout = VideoLayout;
14 17
     this.flipX = true;
15 18
     this.isLocal = true;
16
-    this.peerJid = null;
19
+    this.emitter = emitter;
20
+    SmallVideo.call(this);
17 21
 }
18 22
 
19 23
 LocalVideo.prototype = Object.create(SmallVideo.prototype);
@@ -61,6 +65,7 @@ LocalVideo.prototype.setDisplayName = function(displayName, key) {
61 65
                 $('#localDisplayName').html(defaultLocalDisplayName);
62 66
             }
63 67
         }
68
+        this.updateView();
64 69
     } else {
65 70
         var editButton = createEditDisplayNameButton();
66 71
 
@@ -117,12 +122,14 @@ LocalVideo.prototype.setDisplayName = function(displayName, key) {
117 122
 
118 123
                 editDisplayName.one("focusout", function (e) {
119 124
                     self.VideoLayout.inputDisplayNameHandler(this.value);
125
+                    $('#editDisplayName').hide();
120 126
                 });
121 127
 
122 128
                 editDisplayName.on('keydown', function (e) {
123 129
                     if (e.keyCode === 13) {
124 130
                         e.preventDefault();
125
-                        self.VideoLayout.inputDisplayNameHandler(this.value);
131
+                        $('#editDisplayName').hide();
132
+                        // focusout handler will save display name
126 133
                     }
127 134
                 });
128 135
             });
@@ -130,25 +137,7 @@ LocalVideo.prototype.setDisplayName = function(displayName, key) {
130 137
 };
131 138
 
132 139
 LocalVideo.prototype.inputDisplayNameHandler = function (name) {
133
-    name = UIUtil.escapeHtml(name);
134
-
135
-    NicknameHandler.setNickname(name);
136
-
137
-    var localDisplayName = $('#localDisplayName');
138
-    if (!localDisplayName.is(":visible")) {
139
-        if (NicknameHandler.getNickname()) {
140
-            var meHTML = APP.translation.generateTranslationHTML("me");
141
-            localDisplayName.html(NicknameHandler.getNickname() + " (" +
142
-            meHTML + ")");
143
-        } else {
144
-            var defaultHTML = APP.translation.generateTranslationHTML(
145
-                interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME);
146
-            localDisplayName .html(defaultHTML);
147
-        }
148
-        localDisplayName.show();
149
-    }
150
-
151
-    $('#editDisplayName').hide();
140
+    this.emitter.emit(UIEvents.NICKNAME_CHANGED, UIUtil.escapeHtml(name));
152 141
 };
153 142
 
154 143
 LocalVideo.prototype.createConnectionIndicator = function() {
@@ -158,37 +147,29 @@ LocalVideo.prototype.createConnectionIndicator = function() {
158 147
     this.connectionIndicator = new ConnectionIndicator(this, null);
159 148
 };
160 149
 
161
-LocalVideo.prototype.changeVideo = function (stream, isMuted) {
162
-    var self = this;
150
+LocalVideo.prototype.changeVideo = function (stream) {
151
+    this.stream = stream;
163 152
 
164
-    function localVideoClick(event) {
153
+    let localVideoClick = (event) => {
165 154
         // FIXME: with Temasys plugin event arg is not an event, but
166 155
         // the clicked object itself, so we have to skip this call
167 156
         if (event.stopPropagation) {
168 157
             event.stopPropagation();
169 158
         }
170
-        self.VideoLayout.handleVideoThumbClicked(
171
-            true,
172
-            APP.xmpp.myResource());
173
-    }
159
+        this.VideoLayout.handleVideoThumbClicked(true, this.id);
160
+    };
174 161
 
175
-    var localVideoContainerSelector = $('#localVideoContainer');
162
+    let localVideoContainerSelector = $('#localVideoContainer');
176 163
     localVideoContainerSelector.off('click');
177 164
     localVideoContainerSelector.on('click', localVideoClick);
178 165
 
179
-    if(isMuted) {
180
-        APP.UI.setVideoMute(true);
181
-        return;
182
-    }
183
-    this.flipX = stream.videoType != "screen";
184
-    var localVideo = document.createElement('video');
185
-    localVideo.id = 'localVideo_' +
186
-        APP.RTC.getStreamID(stream.getOriginalStream());
166
+    this.flipX = stream.videoType != "desktop";
167
+    let localVideo = document.createElement('video');
168
+    localVideo.id = 'localVideo_' + stream.getId();
187 169
     if (!RTCBrowserType.isIExplorer()) {
188 170
         localVideo.autoplay = true;
189 171
         localVideo.volume = 0; // is it required if audio is separated ?
190 172
     }
191
-    localVideo.oncontextmenu = function () { return false; };
192 173
 
193 174
     var localVideoContainer = document.getElementById('localVideoWrapper');
194 175
     // Put the new video always in front
@@ -207,29 +188,19 @@ LocalVideo.prototype.changeVideo = function (stream, isMuted) {
207 188
     }
208 189
 
209 190
     // Attach WebRTC stream
210
-    APP.RTC.attachMediaStream(localVideoSelector, stream.getOriginalStream());
191
+    stream.attach(localVideoSelector);
211 192
 
212
-    // Add stream ended handler
213
-    APP.RTC.addMediaStreamInactiveHandler(
214
-        stream.getOriginalStream(), function () {
215
-        // We have to re-select after attach when Temasys plugin is used,
216
-        // because <video> element is replaced with <object>
193
+    let endedHandler = () => {
217 194
         localVideo = $('#' + localVideo.id)[0];
218 195
         localVideoContainer.removeChild(localVideo);
219
-        self.VideoLayout.updateRemovedVideo(APP.xmpp.myResource());
220
-    });
196
+        this.VideoLayout.updateRemovedVideo(this.id);
197
+        stream.off(TrackEvents.TRACK_STOPPED, endedHandler);
198
+    };
199
+    stream.on(TrackEvents.TRACK_STOPPED, endedHandler);
221 200
 };
222 201
 
223
-LocalVideo.prototype.joined = function (jid) {
224
-    this.peerJid = jid;
225
-};
226
-
227
-LocalVideo.prototype.getResourceJid = function () {
228
-    var myResource = APP.xmpp.myResource();
229
-    if (!myResource) {
230
-        console.error("Requested local resource before we're in the MUC");
231
-    }
232
-    return myResource;
202
+LocalVideo.prototype.joined = function (id) {
203
+    this.id = id;
233 204
 };
234 205
 
235
-module.exports = LocalVideo;
206
+export default LocalVideo;

+ 83
- 78
modules/UI/videolayout/RemoteVideo.js 查看文件

@@ -1,28 +1,30 @@
1
-/* global $, APP, require, Strophe, interfaceConfig */
2
-var ConnectionIndicator = require("./ConnectionIndicator");
3
-var SmallVideo = require("./SmallVideo");
4
-var AudioLevels = require("../audio_levels/AudioLevels");
5
-var MediaStreamType = require("../../../service/RTC/MediaStreamTypes");
1
+/* global $, APP, interfaceConfig */
2
+
3
+import ConnectionIndicator from './ConnectionIndicator';
4
+
5
+import SmallVideo from "./SmallVideo";
6
+import AudioLevels from "../audio_levels/AudioLevels";
7
+import UIUtils from "../util/UIUtil";
8
+import UIEvents from '../../../service/UI/UIEvents';
9
+
6 10
 var RTCBrowserType = require("../../RTC/RTCBrowserType");
7
-var UIUtils = require("../util/UIUtil");
8
-var XMPPEvents = require("../../../service/xmpp/XMPPEvents");
9 11
 
10
-function RemoteVideo(peerJid, VideoLayout) {
11
-    this.peerJid = peerJid;
12
-    this.resourceJid = Strophe.getResourceFromJid(peerJid);
13
-    this.videoSpanId = 'participant_' + this.resourceJid;
12
+function RemoteVideo(id, VideoLayout, emitter) {
13
+    this.id = id;
14
+    this.emitter = emitter;
15
+    this.videoSpanId = `participant_${id}`;
14 16
     this.VideoLayout = VideoLayout;
15 17
     this.addRemoteVideoContainer();
16
-    this.connectionIndicator = new ConnectionIndicator(
17
-        this, this.peerJid);
18
+    this.connectionIndicator = new ConnectionIndicator(this, id);
18 19
     this.setDisplayName();
19 20
     var nickfield = document.createElement('span');
20 21
     nickfield.className = "nick";
21
-    nickfield.appendChild(document.createTextNode(this.resourceJid));
22
+    nickfield.appendChild(document.createTextNode(id));
22 23
     this.container.appendChild(nickfield);
23 24
     this.bindHoverHandler();
24 25
     this.flipX = false;
25 26
     this.isLocal = false;
27
+    SmallVideo.call(this);
26 28
 }
27 29
 
28 30
 RemoteVideo.prototype = Object.create(SmallVideo.prototype);
@@ -30,18 +32,20 @@ RemoteVideo.prototype.constructor = RemoteVideo;
30 32
 
31 33
 RemoteVideo.prototype.addRemoteVideoContainer = function() {
32 34
     this.container = RemoteVideo.createContainer(this.videoSpanId);
33
-    if (APP.xmpp.isModerator())
35
+    if (APP.conference.isModerator) {
34 36
         this.addRemoteVideoMenu();
35
-    AudioLevels.updateAudioLevelCanvas(this.peerJid, this.VideoLayout);
37
+    }
38
+    let {thumbWidth, thumbHeight} = this.VideoLayout.calculateThumbnailSize();
39
+    AudioLevels.updateAudioLevelCanvas(this.id, thumbWidth, thumbHeight);
36 40
 
37 41
     return this.container;
38 42
 };
39 43
 
40 44
 /**
41
- * Adds the remote video menu element for the given <tt>jid</tt> in the
45
+ * Adds the remote video menu element for the given <tt>id</tt> in the
42 46
  * given <tt>parentElement</tt>.
43 47
  *
44
- * @param jid the jid indicating the video for which we're adding a menu.
48
+ * @param id the id indicating the video for which we're adding a menu.
45 49
  * @param parentElement the parent element where this menu will be added
46 50
  */
47 51
 
@@ -60,7 +64,7 @@ if (!interfaceConfig.filmStripOnly) {
60 64
 
61 65
         var popupmenuElement = document.createElement('ul');
62 66
         popupmenuElement.className = 'popupmenu';
63
-        popupmenuElement.id = 'remote_popupmenu_' + this.getResourceJid();
67
+        popupmenuElement.id = `remote_popupmenu_${this.id}`;
64 68
         spanElement.appendChild(popupmenuElement);
65 69
 
66 70
         var muteMenuItem = document.createElement('li');
@@ -88,7 +92,7 @@ if (!interfaceConfig.filmStripOnly) {
88 92
                 event.preventDefault();
89 93
             }
90 94
             var isMute = !!self.isMuted;
91
-            APP.xmpp.setMute(self.peerJid, !isMute);
95
+            self.emitter.emit(UIEvents.REMOTE_AUDIO_MUTED, self.id);
92 96
 
93 97
             popupmenuElement.setAttribute('style', 'display:none;');
94 98
 
@@ -117,7 +121,7 @@ if (!interfaceConfig.filmStripOnly) {
117 121
             "data-i18n='videothumbnail.kick'>&nbsp;</div>";
118 122
         ejectLinkItem.innerHTML = ejectIndicator + ' ' + ejectText;
119 123
         ejectLinkItem.onclick = function(){
120
-            APP.xmpp.eject(self.peerJid);
124
+            self.emitter.emit(UIEvents.USER_KICKED, self.id);
121 125
             popupmenuElement.setAttribute('style', 'display:none;');
122 126
         };
123 127
 
@@ -157,48 +161,49 @@ RemoteVideo.prototype.removeRemoteStreamElement =
157 161
     select.remove();
158 162
 
159 163
     console.info((isVideo ? "Video" : "Audio") +
160
-                 " removed " + this.getResourceJid(), select);
164
+                 " removed " + this.id, select);
161 165
 
162 166
     if (isVideo)
163
-        this.VideoLayout.updateRemovedVideo(this.getResourceJid());
167
+        this.VideoLayout.updateRemovedVideo(this.id);
164 168
 };
165 169
 
166 170
 /**
167 171
  * Removes RemoteVideo from the page.
168 172
  */
169 173
 RemoteVideo.prototype.remove = function () {
170
-    console.log("Remove thumbnail", this.peerJid);
174
+    console.log("Remove thumbnail", this.id);
171 175
     this.removeConnectionIndicator();
172 176
     // Make sure that the large video is updated if are removing its
173 177
     // corresponding small video.
174
-    this.VideoLayout.updateRemovedVideo(this.getResourceJid());
178
+    this.VideoLayout.updateRemovedVideo(this.id);
175 179
     // Remove whole container
176
-    if (this.container.parentNode)
180
+    if (this.container.parentNode) {
177 181
         this.container.parentNode.removeChild(this.container);
182
+    }
178 183
 };
179 184
 
180 185
 RemoteVideo.prototype.waitForPlayback = function (sel, stream) {
181 186
 
182 187
     var webRtcStream = stream.getOriginalStream();
183
-    var isVideo = stream.isVideoStream();
188
+    var isVideo = stream.isVideoTrack();
184 189
     if (!isVideo || webRtcStream.id === 'mixedmslabel') {
185 190
         return;
186 191
     }
187 192
 
188 193
     var self = this;
189
-    var resourceJid = this.getResourceJid();
190 194
 
191 195
     // Register 'onplaying' listener to trigger 'videoactive' on VideoLayout
192 196
     // when video playback starts
193 197
     var onPlayingHandler = function () {
194 198
         // FIXME: why do i have to do this for FF?
195 199
         if (RTCBrowserType.isFirefox()) {
196
-            APP.RTC.attachMediaStream(sel, webRtcStream);
200
+            //FIXME: weshould use the lib here
201
+            //APP.RTC.attachMediaStream(sel, webRtcStream);
197 202
         }
198 203
         if (RTCBrowserType.isTemasysPluginUsed()) {
199 204
             sel = self.selectVideoElement();
200 205
         }
201
-        self.VideoLayout.videoactive(sel, resourceJid);
206
+        self.VideoLayout.videoactive(sel, self.id);
202 207
         sel[0].onplaying = null;
203 208
         if (RTCBrowserType.isTemasysPluginUsed()) {
204 209
             // 'currentTime' is used to check if the video has started
@@ -210,39 +215,18 @@ RemoteVideo.prototype.waitForPlayback = function (sel, stream) {
210 215
 };
211 216
 
212 217
 RemoteVideo.prototype.addRemoteStreamElement = function (stream) {
213
-    if (!this.container)
218
+    if (!this.container) {
214 219
         return;
215
-
216
-    var self = this;
217
-    var webRtcStream = stream.getOriginalStream();
218
-    var isVideo = stream.isVideoStream();
219
-    var streamElement = SmallVideo.createStreamElement(stream);
220
-    var newElementId = streamElement.id;
221
-
222
-    // Put new stream element always in front
223
-    UIUtils.prependChild(this.container, streamElement);
224
-
225
-    var sel = $('#' + newElementId);
226
-    sel.hide();
227
-
228
-    // If the container is currently visible we attach the stream.
229
-    if (!isVideo || (this.container.offsetParent !== null && isVideo)) {
230
-        this.waitForPlayback(sel, stream);
231
-
232
-        APP.RTC.attachMediaStream(sel, webRtcStream);
233 220
     }
234 221
 
235
-    APP.RTC.addMediaStreamInactiveHandler(
236
-        webRtcStream, function () {
237
-            console.log('stream ended', this);
222
+    this.stream = stream;
238 223
 
239
-            self.removeRemoteStreamElement(webRtcStream, isVideo, newElementId);
240
-    });
224
+    let isVideo = stream.isVideoTrack();
241 225
 
242 226
     // Add click handler.
243
-    var onClickHandler = function (event) {
227
+    let onClickHandler = (event) => {
244 228
 
245
-        self.VideoLayout.handleVideoThumbClicked(false, self.getResourceJid());
229
+        this.VideoLayout.handleVideoThumbClicked(false, this.id);
246 230
 
247 231
         // On IE we need to populate this handler on video <object>
248 232
         // and it does not give event instance as an argument,
@@ -254,14 +238,39 @@ RemoteVideo.prototype.addRemoteStreamElement = function (stream) {
254 238
         return false;
255 239
     };
256 240
     this.container.onclick = onClickHandler;
241
+
242
+    if(!stream.getOriginalStream())
243
+        return;
244
+
245
+    let streamElement = SmallVideo.createStreamElement(stream);
246
+    let newElementId = streamElement.id;
247
+
248
+    // Put new stream element always in front
249
+    UIUtils.prependChild(this.container, streamElement);
250
+
251
+    let sel = $(`#${newElementId}`);
252
+
253
+    // If the container is currently visible we attach the stream.
254
+    if (!isVideo || (this.container.offsetParent !== null && isVideo)) {
255
+        this.waitForPlayback(sel, stream);
256
+
257
+        stream.attach(sel);
258
+    }
259
+
260
+    // hide element only after stream was (maybe) attached
261
+    // because Temasys plugin requires video element
262
+    // to be visible to attach the stream
263
+    sel.hide();
264
+
257 265
     // reselect
258
-    if (RTCBrowserType.isTemasysPluginUsed())
259
-        sel = $('#' + newElementId);
260
-    sel[0].onclick = onClickHandler;
266
+    if (RTCBrowserType.isTemasysPluginUsed()) {
267
+        sel = $(`#${newElementId}`);
268
+    }
269
+    sel.click(onClickHandler);
261 270
 },
262 271
 
263 272
 /**
264
- * Show/hide peer container for the given resourceJid.
273
+ * Show/hide peer container for the given id.
265 274
  */
266 275
 RemoteVideo.prototype.showPeerContainer = function (state) {
267 276
     if (!this.container)
@@ -275,10 +284,10 @@ RemoteVideo.prototype.showPeerContainer = function (state) {
275 284
             resizeThumbnails = true;
276 285
             $(this.container).show();
277 286
         }
278
-        // Call showAvatar with undefined, so that we'll figure out if avatar
287
+        // Call updateView, so that we'll figure out if avatar
279 288
         // should be displayed based on video muted status and whether or not
280 289
         // it's in the lastN set
281
-        this.showAvatar(undefined);
290
+        this.updateView();
282 291
     }
283 292
     else if ($(this.container).is(':visible') && isHide)
284 293
     {
@@ -294,10 +303,16 @@ RemoteVideo.prototype.showPeerContainer = function (state) {
294 303
 
295 304
     // We want to be able to pin a participant from the contact list, even
296 305
     // if he's not in the lastN set!
297
-    // ContactList.setClickable(resourceJid, !isHide);
306
+    // ContactList.setClickable(id, !isHide);
298 307
 
299 308
 };
300 309
 
310
+RemoteVideo.prototype.updateResolution = function (resolution) {
311
+    if (this.connectionIndicator) {
312
+        this.connectionIndicator.updateResolution(resolution);
313
+    }
314
+};
315
+
301 316
 RemoteVideo.prototype.removeConnectionIndicator = function () {
302 317
     if (this.connectionIndicator)
303 318
         this.connectionIndicator.remove();
@@ -311,12 +326,11 @@ RemoteVideo.prototype.hideConnectionIndicator = function () {
311 326
 /**
312 327
  * Updates the remote video menu.
313 328
  *
314
- * @param jid the jid indicating the video for which we're adding a menu.
329
+ * @param id the id indicating the video for which we're adding a menu.
315 330
  * @param isMuted indicates the current mute state
316 331
  */
317 332
 RemoteVideo.prototype.updateRemoteVideoMenu = function (isMuted) {
318
-    var muteMenuItem
319
-        = $('#remote_popupmenu_' + this.getResourceJid() + '>li>a.mutelink');
333
+    var muteMenuItem = $(`#remote_popupmenu_${this.id}>li>a.mutelink`);
320 334
 
321 335
     var mutedIndicator = "<i class='icon-mic-disabled'></i>";
322 336
 
@@ -399,13 +413,11 @@ RemoteVideo.prototype.setDisplayName = function(displayName, key) {
399 413
         nameSpan.className = 'displayname';
400 414
         $('#' + this.videoSpanId)[0].appendChild(nameSpan);
401 415
 
402
-
403 416
         if (displayName && displayName.length > 0) {
404
-            nameSpan.innerText = displayName;
417
+            nameSpan.innerHTML = displayName;
405 418
         }
406 419
         else
407
-            nameSpan.innerText = interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME;
408
-
420
+            nameSpan.innerHTML = interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME;
409 421
         nameSpan.id = this.videoSpanId + '_name';
410 422
     }
411 423
 };
@@ -423,13 +435,6 @@ RemoteVideo.prototype.removeRemoteVideoMenu = function() {
423 435
     }
424 436
 };
425 437
 
426
-RemoteVideo.prototype.getResourceJid = function () {
427
-    if (!this.resourceJid) {
428
-        console.error("Undefined resource jid");
429
-    }
430
-    return this.resourceJid;
431
-};
432
-
433 438
 RemoteVideo.createContainer = function (spanId) {
434 439
     var container = document.createElement('span');
435 440
     container.id = spanId;
@@ -439,4 +444,4 @@ RemoteVideo.createContainer = function (spanId) {
439 444
 };
440 445
 
441 446
 
442
-module.exports = RemoteVideo;
447
+export default RemoteVideo;

+ 74
- 88
modules/UI/videolayout/SmallVideo.js 查看文件

@@ -1,14 +1,15 @@
1 1
 /* global $, APP, require */
2 2
 /* jshint -W101 */
3
-var Avatar = require("../avatar/Avatar");
4
-var UIUtil = require("../util/UIUtil");
5
-var LargeVideo = require("./LargeVideo");
3
+import Avatar from "../avatar/Avatar";
4
+import UIUtil from "../util/UIUtil";
5
+
6 6
 var RTCBrowserType = require("../../RTC/RTCBrowserType");
7
-var MediaStreamType = require("../../../service/RTC/MediaStreamTypes");
8 7
 
9 8
 function SmallVideo() {
10 9
     this.isMuted = false;
11 10
     this.hasAvatar = false;
11
+    this.isVideoMuted = false;
12
+    this.stream = null;
12 13
 }
13 14
 
14 15
 function setVisibility(selector, show) {
@@ -17,6 +18,16 @@ function setVisibility(selector, show) {
17 18
     }
18 19
 }
19 20
 
21
+/**
22
+ * Indicates if this small video is currently visible.
23
+ *
24
+ * @return <tt>true</tt> if this small video isn't currently visible and
25
+ * <tt>false</tt> - otherwise.
26
+ */
27
+SmallVideo.prototype.isVisible = function () {
28
+    return $('#' + this.videoSpanId).is(':visible');
29
+};
30
+
20 31
 SmallVideo.prototype.showDisplayName = function(isShow) {
21 32
     var nameSpan = $('#' + this.videoSpanId + '>span.displayname').get(0);
22 33
     if (isShow) {
@@ -57,7 +68,7 @@ SmallVideo.prototype.setDeviceAvailabilityIcons = function (devices) {
57 68
 
58 69
 /**
59 70
  * Sets the type of the video displayed by this instance.
60
- * @param videoType 'camera' or 'screen'
71
+ * @param videoType 'camera' or 'desktop'
61 72
  */
62 73
 SmallVideo.prototype.setVideoType = function (videoType) {
63 74
     this.videoType = videoType;
@@ -106,9 +117,10 @@ SmallVideo.prototype.setPresenceStatus = function (statusMsg) {
106 117
  * Creates an audio or video element for a particular MediaStream.
107 118
  */
108 119
 SmallVideo.createStreamElement = function (stream) {
109
-    var isVideo = stream.isVideoStream();
120
+    let isVideo = stream.isVideoTrack();
110 121
 
111
-    var element = isVideo ? document.createElement('video')
122
+    let element = isVideo
123
+        ? document.createElement('video')
112 124
         : document.createElement('audio');
113 125
     if (isVideo) {
114 126
         element.setAttribute("muted", "true");
@@ -118,8 +130,7 @@ SmallVideo.createStreamElement = function (stream) {
118 130
         element.autoplay = true;
119 131
     }
120 132
 
121
-    element.id = (isVideo ? 'remoteVideo_' : 'remoteAudio_') +
122
-        APP.RTC.getStreamID(stream.getOriginalStream());
133
+    element.id = (isVideo ? 'remoteVideo_' : 'remoteAudio_') + stream.getId();
123 134
 
124 135
     element.onplay = function () {
125 136
         console.log("(TIME) Render " + (isVideo ? 'video' : 'audio') + ":\t",
@@ -144,8 +155,8 @@ SmallVideo.prototype.bindHoverHandler = function () {
144 155
         function () {
145 156
             // If the video has been "pinned" by the user we want to
146 157
             // keep the display name on place.
147
-            if (!LargeVideo.isLargeVideoVisible() ||
148
-                !LargeVideo.isCurrentlyOnLarge(self.getResourceJid()))
158
+            if (!self.VideoLayout.isLargeVideoVisible() ||
159
+                !self.VideoLayout.isCurrentlyOnLarge(self.id))
149 160
                 self.showDisplayName(false);
150 161
         }
151 162
     );
@@ -202,10 +213,12 @@ SmallVideo.prototype.showAudioIndicator = function(isMuted) {
202 213
 };
203 214
 
204 215
 /**
205
- * Shows video muted indicator over small videos.
216
+ * Shows video muted indicator over small videos and disables/enables avatar
217
+ * if video muted.
206 218
  */
207
-SmallVideo.prototype.showVideoIndicator = function(isMuted) {
208
-    this.showAvatar(isMuted);
219
+SmallVideo.prototype.setMutedView = function(isMuted) {
220
+    this.isVideoMuted = isMuted;
221
+    this.updateView();
209 222
 
210 223
     var videoMutedSpan = $('#' + this.videoSpanId + '>span.videoMuted');
211 224
 
@@ -232,43 +245,9 @@ SmallVideo.prototype.showVideoIndicator = function(isMuted) {
232 245
         }
233 246
 
234 247
         this.updateIconPositions();
235
-
236 248
     }
237 249
 };
238 250
 
239
-SmallVideo.prototype.enableDominantSpeaker = function (isEnable) {
240
-    var resourceJid = this.getResourceJid();
241
-    var displayName = resourceJid;
242
-    var nameSpan = $('#' + this.videoSpanId + '>span.displayname');
243
-    if (nameSpan.length > 0)
244
-        displayName = nameSpan.html();
245
-
246
-    console.log("UI enable dominant speaker",
247
-        displayName,
248
-        resourceJid,
249
-        isEnable);
250
-
251
-
252
-    if (!this.container) {
253
-        return;
254
-    }
255
-
256
-    if (isEnable) {
257
-        this.showDisplayName(LargeVideo.getState() === "video");
258
-
259
-        if (!this.container.classList.contains("dominantspeaker"))
260
-            this.container.classList.add("dominantspeaker");
261
-    }
262
-    else {
263
-        this.showDisplayName(false);
264
-
265
-        if (this.container.classList.contains("dominantspeaker"))
266
-            this.container.classList.remove("dominantspeaker");
267
-    }
268
-
269
-    this.showAvatar();
270
-};
271
-
272 251
 SmallVideo.prototype.updateIconPositions = function () {
273 252
     var audioMutedSpan = $('#' + this.videoSpanId + '>span.audioMuted');
274 253
     var connectionIndicator = $('#' + this.videoSpanId + '>div.connectionindicator');
@@ -316,13 +295,15 @@ SmallVideo.prototype.createModeratorIndicatorElement = function () {
316 295
 };
317 296
 
318 297
 SmallVideo.prototype.selectVideoElement = function () {
319
-    var videoElem = APP.RTC.getVideoElementName();
298
+    var videoElemName;
320 299
     if (!RTCBrowserType.isTemasysPluginUsed()) {
321
-        return $('#' + this.videoSpanId).find(videoElem);
300
+        videoElemName = 'video';
301
+        return $('#' + this.videoSpanId).find(videoElemName);
322 302
     } else {
303
+        videoElemName = 'object';
323 304
         var matching = $('#' + this.videoSpanId +
324 305
                          (this.isLocal ? '>>' : '>') +
325
-                         videoElem + '>param[value="video"]');
306
+                         videoElemName + '>param[value="video"]');
326 307
         if (matching.length < 2) {
327 308
             return matching.parent();
328 309
         }
@@ -343,11 +324,6 @@ SmallVideo.prototype.selectVideoElement = function () {
343 324
     }
344 325
 };
345 326
 
346
-SmallVideo.prototype.getSrc = function () {
347
-    var videoElement = this.selectVideoElement().get(0);
348
-    return APP.RTC.getVideoSrc(videoElement);
349
-};
350
-
351 327
 SmallVideo.prototype.focus = function(isFocused) {
352 328
     if(!isFocused) {
353 329
         this.container.classList.remove("videoContainerFocused");
@@ -361,66 +337,76 @@ SmallVideo.prototype.hasVideo = function () {
361 337
 };
362 338
 
363 339
 /**
364
- * Hides or shows the user's avatar
340
+ * Hides or shows the user's avatar.
341
+ * This update assumes that large video had been updated and we will
342
+ * reflect it on this small video.
343
+ *
365 344
  * @param show whether we should show the avatar or not
366 345
  * video because there is no dominant speaker and no focused speaker
367 346
  */
368
-SmallVideo.prototype.showAvatar = function (show) {
347
+SmallVideo.prototype.updateView = function () {
369 348
     if (!this.hasAvatar) {
370
-        if (this.peerJid) {
349
+        if (this.id) {
371 350
             // Init avatar
372
-            this.avatarChanged(Avatar.getAvatarUrl(this.peerJid));
351
+            this.avatarChanged(Avatar.getAvatarUrl(this.id));
373 352
         } else {
374
-            console.error("Unable to init avatar - no peerjid", this);
353
+            console.error("Unable to init avatar - no id", this);
375 354
             return;
376 355
         }
377 356
     }
378 357
 
379
-    var resourceJid = this.getResourceJid();
380
-    var video = this.selectVideoElement();
358
+    let video = this.selectVideoElement();
381 359
 
382
-    var avatar = $('#avatar_' + resourceJid);
360
+    let avatar = $(`#avatar_${this.id}`);
383 361
 
384
-    if (show === undefined || show === null) {
385
-        if (!this.isLocal &&
386
-            !this.VideoLayout.isInLastN(resourceJid)) {
387
-            show = true;
388
-        } else {
389
-            // We want to show the avatar when the video is muted or not exists
390
-            // that is when 'true' or 'null' is returned
391
-            show = APP.RTC.isVideoMuted(this.peerJid) !== false;
392
-        }
393
-    }
362
+    var isCurrentlyOnLarge = this.VideoLayout.isCurrentlyOnLarge(this.id);
394 363
 
395
-    if (LargeVideo.showAvatar(resourceJid, show)) {
396
-        setVisibility(avatar, false);
397
-        setVisibility(video, false);
364
+    var showVideo = !this.isVideoMuted && !isCurrentlyOnLarge;
365
+    var showAvatar;
366
+    if ((!this.isLocal
367
+            && !this.VideoLayout.isInLastN(this.id))
368
+        || this.isVideoMuted) {
369
+        showAvatar = true;
398 370
     } else {
399
-        if (video && video.length > 0) {
400
-            setVisibility(video, !show);
401
-        }
402
-        setVisibility(avatar, show);
371
+        // We want to show the avatar when the video is muted or not exists
372
+        // that is when 'true' or 'null' is returned
373
+        showAvatar = !this.stream || this.stream.isMuted();
374
+    }
375
+
376
+    showAvatar = showAvatar && !isCurrentlyOnLarge;
377
+
378
+    if (video && video.length > 0) {
379
+        setVisibility(video, showVideo);
380
+    }
381
+    setVisibility(avatar, showAvatar);
382
+
383
+    var showDisplayName = !showVideo && !showAvatar;
384
+
385
+    if (showDisplayName) {
386
+        this.showDisplayName(this.VideoLayout.isLargeVideoVisible());
387
+    }
388
+    else {
389
+        this.showDisplayName(false);
403 390
     }
404 391
 };
405 392
 
406
-SmallVideo.prototype.avatarChanged = function (thumbUrl) {
393
+SmallVideo.prototype.avatarChanged = function (avatarUrl) {
407 394
     var thumbnail = $('#' + this.videoSpanId);
408
-    var resourceJid = this.getResourceJid();
409
-    var avatar = $('#avatar_' + resourceJid);
395
+    var avatar = $('#avatar_' + this.id);
410 396
     this.hasAvatar = true;
411 397
 
412 398
     // set the avatar in the thumbnail
413 399
     if (avatar && avatar.length > 0) {
414
-        avatar[0].src = thumbUrl;
400
+        avatar[0].src = avatarUrl;
415 401
     } else {
416 402
         if (thumbnail && thumbnail.length > 0) {
417 403
             avatar = document.createElement('img');
418
-            avatar.id = 'avatar_' + resourceJid;
404
+            avatar.id = 'avatar_' + this.id;
419 405
             avatar.className = 'userAvatar';
420
-            avatar.src = thumbUrl;
406
+            avatar.src = avatarUrl;
421 407
             thumbnail.append(avatar);
422 408
         }
423 409
     }
424 410
 };
425 411
 
426
-module.exports = SmallVideo;
412
+export default SmallVideo;

+ 543
- 557
modules/UI/videolayout/VideoLayout.js
文件差异内容过多而无法显示
查看文件


+ 2
- 2
modules/config/BoshAddressChoice.js 查看文件

@@ -1,4 +1,4 @@
1
-var jssha = require('jssha');
1
+var JSSHA = require('jssha');
2 2
 
3 3
 module.exports = {
4 4
     /**
@@ -16,7 +16,7 @@ module.exports = {
16 16
         // This implements the actual choice of an entry in the list based on
17 17
         // roomName. Please consider the implications for existing deployments
18 18
         // before introducing changes.
19
-        var hash = (new jssha(roomName, 'TEXT')).getHash('SHA-1', 'HEX');
19
+        var hash = (new JSSHA(roomName, 'TEXT')).getHash('SHA-1', 'HEX');
20 20
         var n = parseInt("0x"+hash.substr(-6));
21 21
         var idx = n % config.boshList.length;
22 22
         var attemptFirstAddress;

+ 55
- 63
modules/connectionquality/connectionquality.js 查看文件

@@ -3,7 +3,6 @@
3 3
 var EventEmitter = require("events");
4 4
 var eventEmitter = new EventEmitter();
5 5
 var CQEvents = require("../../service/connectionquality/CQEvents");
6
-var XMPPEvents = require("../../service/xmpp/XMPPEvents");
7 6
 var StatisticsEvents = require("../../service/statistics/Events");
8 7
 
9 8
 /**
@@ -18,69 +17,47 @@ var stats = {};
18 17
  */
19 18
 var remoteStats = {};
20 19
 
21
-/**
22
- * Interval for sending statistics to other participants
23
- * @type {null}
24
- */
25
-var sendIntervalId = null;
26
-
27
-
28
-/**
29
- * Start statistics sending.
30
- */
31
-function startSendingStats() {
32
-    sendStats();
33
-    sendIntervalId = setInterval(sendStats, 10000);
34
-}
35
-
36
-/**
37
- * Sends statistics to other participants
38
- */
39
-function sendStats() {
40
-    APP.xmpp.addToPresence("connectionQuality", convertToMUCStats(stats));
41
-}
42
-
43
-/**
44
- * Converts statistics to format for sending through XMPP
45
- * @param stats the statistics
46
- * @returns {{bitrate_donwload: *, bitrate_uplpoad: *, packetLoss_total: *, packetLoss_download: *, packetLoss_upload: *}}
47
- */
48
-function convertToMUCStats(stats) {
49
-    return {
50
-        "bitrate_download": stats.bitrate.download,
51
-        "bitrate_upload": stats.bitrate.upload,
52
-        "packetLoss_total": stats.packetLoss.total,
53
-        "packetLoss_download": stats.packetLoss.download,
54
-        "packetLoss_upload": stats.packetLoss.upload
55
-    };
56
-}
57
-
58 20
 /**
59 21
  * Converts statistics to format used by VideoLayout
60 22
  * @param stats
61 23
  * @returns {{bitrate: {download: *, upload: *}, packetLoss: {total: *, download: *, upload: *}}}
62 24
  */
63 25
 function parseMUCStats(stats) {
26
+    if(!stats || !stats.children || !stats.children.length)
27
+        return null;
28
+    var children = stats.children;
29
+    var extractedStats = {};
30
+    children.forEach((child) => {
31
+        if(child.tagName !== "stat" || !child.attributes)
32
+            return;
33
+        var attrKeys = Object.keys(child.attributes);
34
+        if(!attrKeys || !attrKeys.length)
35
+            return;
36
+        attrKeys.forEach((attr) => {
37
+            extractedStats[attr] = child.attributes[attr];
38
+        });
39
+    });
64 40
     return {
65 41
         bitrate: {
66
-            download: stats.bitrate_download,
67
-            upload: stats.bitrate_upload
42
+            download: extractedStats.bitrate_download,
43
+            upload: extractedStats.bitrate_upload
68 44
         },
69 45
         packetLoss: {
70
-            total: stats.packetLoss_total,
71
-            download: stats.packetLoss_download,
72
-            upload: stats.packetLoss_upload
46
+            total: extractedStats.packetLoss_total,
47
+            download: extractedStats.packetLoss_download,
48
+            upload: extractedStats.packetLoss_upload
73 49
         }
74 50
     };
75 51
 }
76 52
 
77 53
 var ConnectionQuality = {
78 54
     init: function () {
79
-        APP.xmpp.addListener(XMPPEvents.REMOTE_STATS, this.updateRemoteStats);
80
-        APP.statistics.addListener(StatisticsEvents.CONNECTION_STATS,
81
-                                   this.updateLocalStats);
82
-        APP.statistics.addListener(StatisticsEvents.STOP,
83
-                                   this.stopSendingStats);
55
+        APP.statistics.addListener(
56
+            StatisticsEvents.CONNECTION_STATS, this.updateLocalStats
57
+        );
58
+        APP.statistics.addListener(
59
+            StatisticsEvents.STOP, this.stopSendingStats
60
+        );
84 61
     },
85 62
 
86 63
     /**
@@ -90,33 +67,30 @@ var ConnectionQuality = {
90 67
     updateLocalStats: function (data) {
91 68
         stats = data;
92 69
         eventEmitter.emit(CQEvents.LOCALSTATS_UPDATED, 100 - stats.packetLoss.total, stats);
93
-        if (!sendIntervalId) {
94
-            startSendingStats();
95
-        }
96 70
     },
97 71
 
98 72
     /**
99 73
      * Updates remote statistics
100
-     * @param jid the jid associated with the statistics
74
+     * @param id the id associated with the statistics
101 75
      * @param data the statistics
102 76
      */
103
-    updateRemoteStats: function (jid, data) {
104
-        if (!data || !data.packetLoss_total) {
105
-            eventEmitter.emit(CQEvents.REMOTESTATS_UPDATED, jid, null, null);
77
+    updateRemoteStats: function (id, data) {
78
+        data = parseMUCStats(data);
79
+        if (!data || !data.packetLoss || !data.packetLoss.total) {
80
+            eventEmitter.emit(CQEvents.REMOTESTATS_UPDATED, id, null, null);
106 81
             return;
107 82
         }
108
-        remoteStats[jid] = parseMUCStats(data);
83
+        remoteStats[id] = data;
109 84
 
110
-        eventEmitter.emit(CQEvents.REMOTESTATS_UPDATED,
111
-            jid, 100 - data.packetLoss_total, remoteStats[jid]);
85
+        eventEmitter.emit(
86
+            CQEvents.REMOTESTATS_UPDATED, id, 100 - data.packetLoss_total, remoteStats[id]
87
+        );
112 88
     },
113 89
 
114 90
     /**
115 91
      * Stops statistics sending.
116 92
      */
117 93
     stopSendingStats: function () {
118
-        clearInterval(sendIntervalId);
119
-        sendIntervalId = null;
120 94
         //notify UI about stopping statistics gathering
121 95
         eventEmitter.emit(CQEvents.STOP);
122 96
     },
@@ -127,11 +101,29 @@ var ConnectionQuality = {
127 101
     getStats: function () {
128 102
         return stats;
129 103
     },
130
-    
104
+
131 105
     addListener: function (type, listener) {
132 106
         eventEmitter.on(type, listener);
133
-    }
107
+    },
134 108
 
109
+    /**
110
+     * Converts statistics to format for sending through XMPP
111
+     * @param stats the statistics
112
+     * @returns [{tagName: "stat", attributes: {{bitrate_donwload: *}},
113
+     * {tagName: "stat", attributes: {{ bitrate_uplpoad: *}},
114
+     * {tagName: "stat", attributes: {{ packetLoss_total: *}},
115
+     * {tagName: "stat", attributes: {{ packetLoss_download: *}},
116
+     * {tagName: "stat", attributes: {{ packetLoss_upload: *}}]
117
+     */
118
+    convertToMUCStats: function (stats) {
119
+        return [
120
+            {tagName: "stat", attributes: {"bitrate_download": stats.bitrate.download}},
121
+            {tagName: "stat", attributes: {"bitrate_upload": stats.bitrate.upload}},
122
+            {tagName: "stat", attributes: {"packetLoss_total": stats.packetLoss.total}},
123
+            {tagName: "stat", attributes: {"packetLoss_download": stats.packetLoss.download}},
124
+            {tagName: "stat", attributes: {"packetLoss_upload": stats.packetLoss.upload}}
125
+        ];
126
+    }
135 127
 };
136 128
 
137
-module.exports = ConnectionQuality;
129
+module.exports = ConnectionQuality;

+ 0
- 407
modules/desktopsharing/ScreenObtainer.js 查看文件

@@ -1,407 +0,0 @@
1
-/* global config, APP, chrome, $, alert */
2
-/* jshint -W003 */
3
-var RTCBrowserType = require("../RTC/RTCBrowserType");
4
-var AdapterJS = require("../RTC/adapter.screenshare");
5
-var DesktopSharingEventTypes
6
-    = require("../../service/desktopsharing/DesktopSharingEventTypes");
7
-
8
-/**
9
- * Indicates whether the Chrome desktop sharing extension is installed.
10
- * @type {boolean}
11
- */
12
-var chromeExtInstalled = false;
13
-
14
-/**
15
- * Indicates whether an update of the Chrome desktop sharing extension is
16
- * required.
17
- * @type {boolean}
18
- */
19
-var chromeExtUpdateRequired = false;
20
-
21
-/**
22
- * Whether the jidesha extension for firefox is installed for the domain on
23
- * which we are running. Null designates an unknown value.
24
- * @type {null}
25
- */
26
-var firefoxExtInstalled = null;
27
-
28
-/**
29
- * If set to true, detection of an installed firefox extension will be started
30
- * again the next time obtainScreenOnFirefox is called (e.g. next time the
31
- * user tries to enable screen sharing).
32
- */
33
-var reDetectFirefoxExtension = false;
34
-
35
-/**
36
- * Handles obtaining a stream from a screen capture on different browsers.
37
- */
38
-function ScreenObtainer(){
39
-}
40
-
41
-/**
42
- * The EventEmitter to use to emit events.
43
- * @type {null}
44
- */
45
-ScreenObtainer.prototype.eventEmitter = null;
46
-
47
-/**
48
- * Initializes the function used to obtain a screen capture (this.obtainStream).
49
- *
50
- * If the browser is Chrome, it uses the value of
51
- * 'config.desktopSharingChromeMethod' (or 'config.desktopSharing') to * decide
52
- * whether to use the a Chrome extension (if the value is 'ext'), use the
53
- * "screen" media source (if the value is 'webrtc'), or disable screen capture
54
- * (if the value is other).
55
- * Note that for the "screen" media source to work the
56
- * 'chrome://flags/#enable-usermedia-screen-capture' flag must be set.
57
- */
58
-ScreenObtainer.prototype.init = function(eventEmitter) {
59
-    this.eventEmitter = eventEmitter;
60
-    var obtainDesktopStream = null;
61
-
62
-    if (RTCBrowserType.isFirefox())
63
-        initFirefoxExtensionDetection();
64
-
65
-    // TODO remove this, config.desktopSharing is deprecated.
66
-    var chromeMethod =
67
-        (config.desktopSharingChromeMethod || config.desktopSharing);
68
-
69
-    if (RTCBrowserType.isTemasysPluginUsed()) {
70
-        if (!AdapterJS.WebRTCPlugin.plugin.HasScreensharingFeature) {
71
-            console.info("Screensharing not supported by this plugin version");
72
-        } else if (!AdapterJS.WebRTCPlugin.plugin.isScreensharingAvailable) {
73
-            console.info(
74
-                "Screensharing not available with Temasys plugin on this site");
75
-        } else {
76
-            obtainDesktopStream = obtainWebRTCScreen;
77
-            console.info("Using Temasys plugin for desktop sharing");
78
-        }
79
-    } else if (RTCBrowserType.isChrome()) {
80
-        if (chromeMethod == "ext") {
81
-            if (RTCBrowserType.getChromeVersion() >= 34) {
82
-                obtainDesktopStream = obtainScreenFromExtension;
83
-                console.info("Using Chrome extension for desktop sharing");
84
-                initChromeExtension();
85
-            } else {
86
-                console.info("Chrome extension not supported until ver 34");
87
-            }
88
-        } else if (chromeMethod == "webrtc") {
89
-            obtainDesktopStream = obtainWebRTCScreen;
90
-            console.info("Using Chrome WebRTC for desktop sharing");
91
-        }
92
-    } else if (RTCBrowserType.isFirefox()) {
93
-        if (config.desktopSharingFirefoxDisabled) {
94
-            obtainDesktopStream = null;
95
-        } else if (window.location.protocol === "http:"){
96
-            console.log("Screen sharing is not supported over HTTP. Use of " +
97
-                "HTTPS is required.");
98
-            obtainDesktopStream = null;
99
-        } else {
100
-            obtainDesktopStream = this.obtainScreenOnFirefox;
101
-        }
102
-
103
-    }
104
-
105
-    if (!obtainDesktopStream) {
106
-        console.info("Desktop sharing disabled");
107
-    }
108
-
109
-    ScreenObtainer.prototype.obtainStream = obtainDesktopStream;
110
-};
111
-
112
-ScreenObtainer.prototype.obtainStream = null;
113
-
114
-/**
115
- * Checks whether obtaining a screen capture is supported in the current
116
- * environment.
117
- * @returns {boolean}
118
- */
119
-ScreenObtainer.prototype.isSupported = function() {
120
-    return !!this.obtainStream;
121
-};
122
-
123
-/**
124
- * Obtains a desktop stream using getUserMedia.
125
- * For this to work on Chrome, the
126
- * 'chrome://flags/#enable-usermedia-screen-capture' flag must be enabled.
127
- *
128
- * On firefox, the document's domain must be white-listed in the
129
- * 'media.getusermedia.screensharing.allowed_domains' preference in
130
- * 'about:config'.
131
- */
132
-function obtainWebRTCScreen(streamCallback, failCallback) {
133
-    APP.RTC.getUserMediaWithConstraints(
134
-        ['screen'],
135
-        streamCallback,
136
-        failCallback
137
-    );
138
-}
139
-
140
-/**
141
- * Constructs inline install URL for Chrome desktop streaming extension.
142
- * The 'chromeExtensionId' must be defined in config.js.
143
- * @returns {string}
144
- */
145
-function getWebStoreInstallUrl()
146
-{
147
-    //TODO remove chromeExtensionId (deprecated)
148
-    return "https://chrome.google.com/webstore/detail/" +
149
-        (config.desktopSharingChromeExtId || config.chromeExtensionId);
150
-}
151
-
152
-/**
153
- * Checks whether an update of the Chrome extension is required.
154
- * @param minVersion minimal required version
155
- * @param extVersion current extension version
156
- * @returns {boolean}
157
- */
158
-function isUpdateRequired(minVersion, extVersion) {
159
-    try {
160
-        var s1 = minVersion.split('.');
161
-        var s2 = extVersion.split('.');
162
-
163
-        var len = Math.max(s1.length, s2.length);
164
-        for (var i = 0; i < len; i++) {
165
-            var n1 = 0,
166
-                n2 = 0;
167
-
168
-            if (i < s1.length)
169
-                n1 = parseInt(s1[i]);
170
-            if (i < s2.length)
171
-                n2 = parseInt(s2[i]);
172
-
173
-            if (isNaN(n1) || isNaN(n2)) {
174
-                return true;
175
-            } else if (n1 !== n2) {
176
-                return n1 > n2;
177
-            }
178
-        }
179
-
180
-        // will happen if both versions have identical numbers in
181
-        // their components (even if one of them is longer, has more components)
182
-        return false;
183
-    }
184
-    catch (e) {
185
-        console.error("Failed to parse extension version", e);
186
-        APP.UI.messageHandler.showError("dialog.error",
187
-            "dialog.detectext");
188
-        return true;
189
-    }
190
-}
191
-
192
-function checkChromeExtInstalled(callback) {
193
-    if (!chrome || !chrome.runtime) {
194
-        // No API, so no extension for sure
195
-        callback(false, false);
196
-        return;
197
-    }
198
-    chrome.runtime.sendMessage(
199
-        //TODO: remove chromeExtensionId (deprecated)
200
-        (config.desktopSharingChromeExtId || config.chromeExtensionId),
201
-        { getVersion: true },
202
-        function (response) {
203
-            if (!response || !response.version) {
204
-                // Communication failure - assume that no endpoint exists
205
-                console.warn(
206
-                    "Extension not installed?: ", chrome.runtime.lastError);
207
-                callback(false, false);
208
-                return;
209
-            }
210
-            // Check installed extension version
211
-            var extVersion = response.version;
212
-            console.log('Extension version is: ' + extVersion);
213
-            //TODO: remove minChromeExtVersion (deprecated)
214
-            var updateRequired
215
-                = isUpdateRequired(
216
-                    (config.desktopSharingChromeMinExtVersion ||
217
-                        config.minChromeExtVersion),
218
-                    extVersion);
219
-            callback(!updateRequired, updateRequired);
220
-        }
221
-    );
222
-}
223
-
224
-function doGetStreamFromExtension(streamCallback, failCallback) {
225
-    // Sends 'getStream' msg to the extension.
226
-    // Extension id must be defined in the config.
227
-    chrome.runtime.sendMessage(
228
-        //TODO: remove chromeExtensionId (deprecated)
229
-        (config.desktopSharingChromeExtId || config.chromeExtensionId),
230
-        {
231
-            getStream: true,
232
-            //TODO: remove desktopSharingSources (deprecated).
233
-            sources: (config.desktopSharingChromeSources ||
234
-                config.desktopSharingSources)
235
-        },
236
-        function (response) {
237
-            if (!response) {
238
-                failCallback(chrome.runtime.lastError);
239
-                return;
240
-            }
241
-            console.log("Response from extension: " + response);
242
-            if (response.streamId) {
243
-                APP.RTC.getUserMediaWithConstraints(
244
-                    ['desktop'],
245
-                    function (stream) {
246
-                        streamCallback(stream);
247
-                    },
248
-                    failCallback,
249
-                    null, null, null,
250
-                    response.streamId);
251
-            } else {
252
-                failCallback("Extension failed to get the stream");
253
-            }
254
-        }
255
-    );
256
-}
257
-
258
-/**
259
- * Asks Chrome extension to call chooseDesktopMedia and gets chrome 'desktop'
260
- * stream for returned stream token.
261
- */
262
-function obtainScreenFromExtension(streamCallback, failCallback) {
263
-    if (chromeExtInstalled) {
264
-        doGetStreamFromExtension(streamCallback, failCallback);
265
-    } else {
266
-        if (chromeExtUpdateRequired) {
267
-            alert(
268
-                'Jitsi Desktop Streamer requires update. ' +
269
-                'Changes will take effect after next Chrome restart.');
270
-        }
271
-
272
-        chrome.webstore.install(
273
-            getWebStoreInstallUrl(),
274
-            function (arg) {
275
-                console.log("Extension installed successfully", arg);
276
-                chromeExtInstalled = true;
277
-                // We need to give a moment for the endpoint to become available
278
-                window.setTimeout(function () {
279
-                    doGetStreamFromExtension(streamCallback, failCallback);
280
-                }, 500);
281
-            },
282
-            function (arg) {
283
-                console.log("Failed to install the extension", arg);
284
-                failCallback(arg);
285
-                APP.UI.messageHandler.showError("dialog.error",
286
-                    "dialog.failtoinstall");
287
-            }
288
-        );
289
-    }
290
-}
291
-
292
-/**
293
- * Initializes <link rel=chrome-webstore-item /> with extension id set in
294
- * config.js to support inline installs. Host site must be selected as main
295
- * website of published extension.
296
- */
297
-function initInlineInstalls()
298
-{
299
-    $("link[rel=chrome-webstore-item]").attr("href", getWebStoreInstallUrl());
300
-}
301
-
302
-function initChromeExtension() {
303
-    // Initialize Chrome extension inline installs
304
-    initInlineInstalls();
305
-    // Check if extension is installed
306
-    checkChromeExtInstalled(function (installed, updateRequired) {
307
-        chromeExtInstalled = installed;
308
-        chromeExtUpdateRequired = updateRequired;
309
-        console.info(
310
-            "Chrome extension installed: " + chromeExtInstalled +
311
-            " updateRequired: " + chromeExtUpdateRequired);
312
-    });
313
-}
314
-
315
-/**
316
- * Obtains a screen capture stream on Firefox.
317
- * @param callback
318
- * @param errorCallback
319
- */
320
-ScreenObtainer.prototype.obtainScreenOnFirefox =
321
-       function (callback, errorCallback) {
322
-    var self = this;
323
-    var extensionRequired = false;
324
-    if (config.desktopSharingFirefoxMaxVersionExtRequired === -1 ||
325
-        (config.desktopSharingFirefoxMaxVersionExtRequired >= 0 &&
326
-            RTCBrowserType.getFirefoxVersion() <=
327
-                config.desktopSharingFirefoxMaxVersionExtRequired)) {
328
-        extensionRequired = true;
329
-        console.log("Jidesha extension required on firefox version " +
330
-            RTCBrowserType.getFirefoxVersion());
331
-    }
332
-
333
-    if (!extensionRequired || firefoxExtInstalled === true) {
334
-        obtainWebRTCScreen(callback, errorCallback);
335
-        return;
336
-    }
337
-
338
-    if (reDetectFirefoxExtension) {
339
-        reDetectFirefoxExtension = false;
340
-        initFirefoxExtensionDetection();
341
-    }
342
-
343
-    // Give it some (more) time to initialize, and assume lack of extension if
344
-    // it hasn't.
345
-    if (firefoxExtInstalled === null) {
346
-        window.setTimeout(
347
-            function() {
348
-                if (firefoxExtInstalled === null)
349
-                    firefoxExtInstalled = false;
350
-                self.obtainScreenOnFirefox(callback, errorCallback);
351
-            },
352
-            300
353
-        );
354
-        console.log("Waiting for detection of jidesha on firefox to finish.");
355
-        return;
356
-    }
357
-
358
-    // We need an extension and it isn't installed.
359
-
360
-    // Make sure we check for the extension when the user clicks again.
361
-    firefoxExtInstalled = null;
362
-    reDetectFirefoxExtension = true;
363
-
364
-    // Prompt the user to install the extension
365
-    this.eventEmitter.emit(DesktopSharingEventTypes.FIREFOX_EXTENSION_NEEDED,
366
-                           config.desktopSharingFirefoxExtensionURL);
367
-
368
-    // Make sure desktopsharing knows that we failed, so that it doesn't get
369
-    // stuck in 'switching' mode.
370
-    errorCallback('Firefox extension required.');
371
-};
372
-
373
-/**
374
- * Starts the detection of an installed jidesha extension for firefox.
375
- */
376
-function initFirefoxExtensionDetection() {
377
-    if (config.desktopSharingFirefoxDisabled) {
378
-        return;
379
-    }
380
-    if (firefoxExtInstalled === false || firefoxExtInstalled === true)
381
-        return;
382
-    if (!config.desktopSharingFirefoxExtId) {
383
-        firefoxExtInstalled = false;
384
-        return;
385
-    }
386
-
387
-    var img = document.createElement('img');
388
-    img.onload = function(){
389
-        console.log("Detected firefox screen sharing extension.");
390
-        firefoxExtInstalled = true;
391
-    };
392
-    img.onerror = function(){
393
-        console.log("Detected lack of firefox screen sharing extension.");
394
-        firefoxExtInstalled = false;
395
-    };
396
-
397
-    // The jidesha extension exposes an empty image file under the url:
398
-    // "chrome://EXT_ID/content/DOMAIN.png"
399
-    // Where EXT_ID is the ID of the extension with "@" replaced by ".", and
400
-    // DOMAIN is a domain whitelisted by the extension.
401
-    var src = "chrome://" +
402
-        (config.desktopSharingFirefoxExtId.replace('@', '.')) +
403
-        "/content/" + document.location.hostname + ".png";
404
-    img.setAttribute('src', src);
405
-}
406
-
407
-module.exports = ScreenObtainer;

+ 57
- 60
modules/desktopsharing/desktopsharing.js 查看文件

@@ -1,10 +1,8 @@
1
-/* global APP, config */
1
+/* global APP, JitsiMeetJS, config */
2 2
 var EventEmitter = require("events");
3
-var DesktopSharingEventTypes
4
-    = require("../../service/desktopsharing/DesktopSharingEventTypes");
5
-var RTCBrowserType = require("../RTC/RTCBrowserType");
6
-var RTCEvents = require("../../service/RTC/RTCEvents");
7
-var ScreenObtainer = require("./ScreenObtainer");
3
+import DSEvents from '../../service/desktopsharing/DesktopSharingEventTypes';
4
+
5
+const TrackEvents = JitsiMeetJS.events.track;
8 6
 
9 7
 /**
10 8
  * Indicates that desktop stream is currently in use (for toggle purpose).
@@ -20,22 +18,19 @@ var isUsingScreenStream = false;
20 18
 var switchInProgress = false;
21 19
 
22 20
 /**
23
- * Used to obtain the screen sharing stream from the browser.
21
+ * true if desktop sharing is enabled and false otherwise.
24 22
  */
25
-var screenObtainer = new ScreenObtainer();
23
+var isEnabled = false;
26 24
 
27 25
 var eventEmitter = new EventEmitter();
28 26
 
29 27
 function streamSwitchDone() {
30 28
     switchInProgress = false;
31
-    eventEmitter.emit(
32
-        DesktopSharingEventTypes.SWITCHING_DONE,
33
-        isUsingScreenStream);
29
+    eventEmitter.emit(DSEvents.SWITCHING_DONE, isUsingScreenStream);
34 30
 }
35 31
 
36
-function newStreamCreated(stream) {
37
-    eventEmitter.emit(DesktopSharingEventTypes.NEW_STREAM_CREATED,
38
-        stream, isUsingScreenStream, streamSwitchDone);
32
+function newStreamCreated(track) {
33
+    eventEmitter.emit(DSEvents.NEW_STREAM_CREATED, track, streamSwitchDone);
39 34
 }
40 35
 
41 36
 function getVideoStreamFailed(error) {
@@ -50,36 +45,31 @@ function getDesktopStreamFailed(error) {
50 45
     switchInProgress = false;
51 46
 }
52 47
 
53
-function onEndedHandler(stream) {
48
+function onEndedHandler() {
54 49
     if (!switchInProgress && isUsingScreenStream) {
55 50
         APP.desktopsharing.toggleScreenSharing();
56 51
     }
57
-
58
-    APP.RTC.removeMediaStreamInactiveHandler(stream, onEndedHandler);
59 52
 }
60 53
 
61 54
 module.exports = {
62 55
     isUsingScreenStream: function () {
63 56
         return isUsingScreenStream;
64 57
     },
65
-
58
+    /**
59
+     * Initializes the desktop sharing module.
60
+     * @param {boolean} <tt>true</tt> if desktop sharing feature is available
61
+     * and enabled.
62
+     */
63
+    init: function (enabled) {
64
+        isEnabled = enabled;
65
+    },
66 66
     /**
67 67
      * @returns {boolean} <tt>true</tt> if desktop sharing feature is available
68 68
      *          and enabled.
69 69
      */
70 70
     isDesktopSharingEnabled: function () {
71
-        return screenObtainer.isSupported();
72
-    },
73
-    
74
-    init: function () {
75
-        // Called when RTC finishes initialization
76
-        APP.RTC.addListener(RTCEvents.RTC_READY,
77
-            function() {
78
-                screenObtainer.init(eventEmitter);
79
-                eventEmitter.emit(DesktopSharingEventTypes.INIT);
80
-            });
71
+        return isEnabled;
81 72
     },
82
-
83 73
     addListener: function (type, listener) {
84 74
         eventEmitter.on(type, listener);
85 75
     },
@@ -95,43 +85,50 @@ module.exports = {
95 85
         if (switchInProgress) {
96 86
             console.warn("Switch in progress.");
97 87
             return;
98
-        } else if (!screenObtainer.isSupported()) {
88
+        } else if (!this.isDesktopSharingEnabled()) {
99 89
             console.warn("Cannot toggle screen sharing: not supported.");
100 90
             return;
101 91
         }
102 92
         switchInProgress = true;
103
-
93
+        let type;
104 94
         if (!isUsingScreenStream) {
105 95
             // Switch to desktop stream
106
-            screenObtainer.obtainStream(
107
-                function (stream) {
108
-                    // We now use screen stream
109
-                    isUsingScreenStream = true;
110
-                    // Hook 'ended' event to restore camera
111
-                    // when screen stream stops
112
-                    APP.RTC.addMediaStreamInactiveHandler(
113
-                        stream, onEndedHandler);
114
-                    newStreamCreated(stream);
115
-                },
116
-                getDesktopStreamFailed);
96
+            type = "desktop";
117 97
         } else {
118
-            // Disable screen stream
119
-            APP.RTC.getUserMediaWithConstraints(
120
-                ['video'],
121
-                function (stream) {
122
-                    // We are now using camera stream
123
-                    isUsingScreenStream = false;
124
-                    newStreamCreated(stream);
125
-                },
126
-                getVideoStreamFailed,
127
-                config.resolution || '360'
128
-            );
98
+            type = "video";
129 99
         }
130
-    },
131
-    /*
132
-     * Exports the event emitter to allow use by ScreenObtainer. Not for outside
133
-     * use.
134
-     */
135
-    eventEmitter: eventEmitter
100
+        var fail = (error) => {
101
+            if (type === 'desktop') {
102
+                getDesktopStreamFailed(error);
103
+            } else {
104
+                getVideoStreamFailed(error);
105
+            }
106
+        };
107
+        APP.conference.createLocalTracks(type).then((tracks) => {
108
+            // FIXME does it mean that 'not track.length' == GUM failed ?
109
+            // And will this ever happen if promise is supposed to fail in GUM
110
+            // failed case ?
111
+            if (!tracks.length) {
112
+                fail();
113
+                return;
114
+            }
115
+            let stream = tracks[0];
116
+
117
+            // We now use screen stream
118
+            isUsingScreenStream = type === "desktop";
119
+            if (isUsingScreenStream) {
120
+                stream.on(TrackEvents.TRACK_STOPPED, onEndedHandler);
121
+            }
122
+            newStreamCreated(stream);
123
+        }).catch((error) => {
124
+            if(error === JitsiMeetJS.errors.track.FIREFOX_EXTENSION_NEEDED)
125
+            {
126
+                eventEmitter.emit(
127
+                    DSEvents.FIREFOX_EXTENSION_NEEDED,
128
+                    config.desktopSharingFirefoxExtensionURL);
129
+                return;
130
+            }
131
+            fail(error);
132
+        });
133
+    }
136 134
 };
137
-

+ 4
- 8
modules/keyboardshortcut/keyboardshortcut.js 查看文件

@@ -21,20 +21,18 @@ function initShortcutHandlers() {
21 21
         77: {
22 22
             character: "M",
23 23
             id: "mutePopover",
24
-            function: APP.UI.toggleAudio
24
+            function: APP.conference.toggleAudioMuted
25 25
         },
26 26
         84: {
27 27
             character: "T",
28 28
             function: function() {
29
-                if(!APP.RTC.localAudio.isMuted()) {
30
-                    APP.UI.toggleAudio();
31
-                }
29
+                APP.conference.muteAudio(true);
32 30
             }
33 31
         },
34 32
         86: {
35 33
             character: "V",
36 34
             id: "toggleVideoPopover",
37
-            function: APP.UI.toggleVideo
35
+            function: APP.conference.toggleVideoMuted
38 36
         }
39 37
     };
40 38
 }
@@ -67,9 +65,7 @@ var KeyboardShortcut = {
67 65
                 $(":focus").is("input[type=password]") ||
68 66
                 $(":focus").is("textarea"))) {
69 67
                 if(e.which === "T".charCodeAt(0)) {
70
-                    if(APP.RTC.localAudio.isMuted()) {
71
-                        APP.UI.toggleAudio();
72
-                    }
68
+                    APP.conference.muteAudio(true);
73 69
                 }
74 70
             }
75 71
         };

+ 0
- 128
modules/members/MemberList.js 查看文件

@@ -1,128 +0,0 @@
1
-/* global APP, require, $ */
2
-
3
-/**
4
- * This module is meant to (eventually) contain and manage all information
5
- * about members/participants of the conference, so that other modules don't
6
- * have to do it on their own, and so that other modules can access members'
7
- * information from a single place.
8
- *
9
- * Currently this module only manages information about the support of jingle
10
- * DTMF of the members. Other fields, as well as accessor methods are meant to
11
- * be added as needed.
12
- */
13
-
14
-var XMPPEvents = require("../../service/xmpp/XMPPEvents");
15
-var Events = require("../../service/members/Events");
16
-var EventEmitter = require("events");
17
-
18
-var eventEmitter = new EventEmitter();
19
-
20
-/**
21
- * The actual container.
22
- */
23
-var members = {};
24
-
25
-/**
26
- * There is at least one member that supports DTMF (i.e. is jigasi).
27
- */
28
-var atLeastOneDtmf = false;
29
-
30
-
31
-function registerListeners() {
32
-    APP.xmpp.addListener(XMPPEvents.MUC_MEMBER_JOINED, onMucMemberJoined);
33
-    APP.xmpp.addListener(XMPPEvents.MUC_MEMBER_LEFT, onMucMemberLeft);
34
-}
35
-
36
-/**
37
- * Handles a new member joining the MUC.
38
- */
39
-function onMucMemberJoined(jid, id, displayName) {
40
-    var member = {
41
-        displayName: displayName
42
-    };
43
-
44
-    APP.xmpp.getConnection().disco.info(
45
-        jid, "" /* node */, function(iq) { onDiscoInfoReceived(jid, iq); });
46
-
47
-    members[jid] = member;
48
-}
49
-
50
-/**
51
- * Handles a member leaving the MUC.
52
- */
53
-function onMucMemberLeft(jid) {
54
-    delete members[jid];
55
-    updateAtLeastOneDtmf();
56
-}
57
-
58
-/**
59
- * Handles the reception of a disco#info packet from a particular JID.
60
- * @param jid the JID sending the packet.
61
- * @param iq the packet.
62
- */
63
-function onDiscoInfoReceived(jid, iq) {
64
-    if (!members[jid])
65
-        return;
66
-
67
-    var supportsDtmf
68
-        = $(iq).find('>query>feature[var="urn:xmpp:jingle:dtmf:0"]').length > 0;
69
-    updateDtmf(jid, supportsDtmf);
70
-}
71
-
72
-/**
73
- * Updates the 'supportsDtmf' field for a member.
74
- * @param jid the jid of the member.
75
- * @param newValue the new value for the 'supportsDtmf' field.
76
- */
77
-function updateDtmf(jid, newValue) {
78
-    var oldValue = members[jid].supportsDtmf;
79
-    members[jid].supportsDtmf = newValue;
80
-
81
-    if (newValue != oldValue) {
82
-        updateAtLeastOneDtmf();
83
-    }
84
-}
85
-
86
-/**
87
- * Checks each member's 'supportsDtmf' field and updates
88
- * 'atLastOneSupportsDtmf'.
89
- */
90
-function updateAtLeastOneDtmf() {
91
-    var newAtLeastOneDtmf = false;
92
-    for (var key in members) {
93
-        if (typeof members[key].supportsDtmf !== 'undefined'
94
-            && members[key].supportsDtmf) {
95
-            newAtLeastOneDtmf= true;
96
-            break;
97
-        }
98
-    }
99
-
100
-    if (atLeastOneDtmf != newAtLeastOneDtmf) {
101
-        atLeastOneDtmf = newAtLeastOneDtmf;
102
-        eventEmitter.emit(Events.DTMF_SUPPORT_CHANGED, atLeastOneDtmf);
103
-    }
104
-}
105
-
106
-
107
-/**
108
- * Exported interface.
109
- */
110
-var Members = {
111
-    start: function() {
112
-        registerListeners();
113
-    },
114
-    addListener: function(type, listener) {
115
-        eventEmitter.on(type, listener);
116
-    },
117
-    removeListener: function (type, listener) {
118
-        eventEmitter.removeListener(type, listener);
119
-    },
120
-    size: function () {
121
-        return Object.keys(members).length;
122
-    },
123
-    getMembers: function () {
124
-        return members;
125
-    }
126
-};
127
-
128
-module.exports = Members;

+ 12
- 23
modules/settings/Settings.js 查看文件

@@ -1,11 +1,9 @@
1
-var UsernameGenerator = require('../util/UsernameGenerator');
1
+import {generateUsername} from '../util/UsernameGenerator';
2 2
 
3 3
 var email = '';
4 4
 var displayName = '';
5 5
 var userId;
6 6
 var language = null;
7
-var callStatsUserName;
8
-
9 7
 
10 8
 function supportsLocalStorage() {
11 9
     try {
@@ -30,25 +28,16 @@ if (supportsLocalStorage()) {
30 28
         console.log("generated id", window.localStorage.jitsiMeetId);
31 29
     }
32 30
 
33
-    if (!window.localStorage.callStatsUserName) {
34
-        window.localStorage.callStatsUserName
35
-            = UsernameGenerator.generateUsername();
36
-        console.log('generated callstats uid',
37
-            window.localStorage.callStatsUserName);
38
-
39
-    }
40 31
     userId = window.localStorage.jitsiMeetId || '';
41
-    callStatsUserName = window.localStorage.callStatsUserName;
42 32
     email = window.localStorage.email || '';
43 33
     displayName = window.localStorage.displayname || '';
44 34
     language = window.localStorage.language;
45 35
 } else {
46 36
     console.log("local storage is not supported");
47 37
     userId = generateUniqueId();
48
-    callStatsUserName = UsernameGenerator.generateUsername();
49 38
 }
50 39
 
51
-var Settings = {
40
+export default {
52 41
 
53 42
     /**
54 43
      * Sets the local user display name and saves it to local storage
@@ -57,6 +46,9 @@ var Settings = {
57 46
      * @returns {string} the display name we just set
58 47
      */
59 48
     setDisplayName: function (newDisplayName) {
49
+        if (displayName === newDisplayName) {
50
+            return displayName;
51
+        }
60 52
         displayName = newDisplayName;
61 53
         window.localStorage.displayname = displayName;
62 54
         return displayName;
@@ -70,20 +62,16 @@ var Settings = {
70 62
         return displayName;
71 63
     },
72 64
 
73
-    /**
74
-     * Returns fake username for callstats
75
-     * @returns {string} fake username for callstats
76
-     */
77
-    getCallStatsUserName: function () {
78
-        return callStatsUserName;
79
-    },
80
-
81 65
     setEmail: function (newEmail) {
82 66
         email = newEmail;
83 67
         window.localStorage.email = newEmail;
84 68
         return email;
85 69
     },
86 70
 
71
+    getEmail: function () {
72
+        return email;
73
+    },
74
+
87 75
     getSettings: function () {
88 76
         return {
89 77
             email: email,
@@ -92,10 +80,11 @@ var Settings = {
92 80
             language: language
93 81
         };
94 82
     },
83
+    getLanguage () {
84
+        return language;
85
+    },
95 86
     setLanguage: function (lang) {
96 87
         language = lang;
97 88
         window.localStorage.language = lang;
98 89
     }
99 90
 };
100
-
101
-module.exports = Settings;

+ 25
- 26
modules/statistics/AnalyticsAdapter.js 查看文件

@@ -1,6 +1,4 @@
1
-/* global config */
2
-
3
-var ScriptUtil = require('../util/ScriptUtil');
1
+/* global config JitsiMeetJS */
4 2
 
5 3
 // Load the integration of a third-party analytics API such as Google Analytics.
6 4
 // Since we cannot guarantee the quality of the third-party service (e.g. their
@@ -11,37 +9,38 @@ var ScriptUtil = require('../util/ScriptUtil');
11 9
 // its implementation asynchronously anyway so it makes sense to append the
12 10
 // loading on our side rather than prepend it.
13 11
 if (config.disableThirdPartyRequests !== true) {
14
-    ScriptUtil.loadScript(
12
+    JitsiMeetJS.util.ScriptUtil.loadScript(
15 13
             'analytics.js?v=1',
16 14
             /* async */ true,
17 15
             /* prepend */ false);
18 16
 }
19 17
 
20
-// NoopAnalytics
21
-function NoopAnalytics() {}
18
+class NoopAnalytics {
19
+    sendEvent () {}
20
+}
22 21
 
23
-NoopAnalytics.prototype.sendEvent = function () {};
22
+// XXX Since we asynchronously load the integration of the analytics API and the
23
+// analytics API may asynchronously load its implementation (e.g. Google
24
+// Analytics), we cannot make the decision with respect to which analytics
25
+// implementation we will use here and we have to postpone it i.e. we will make
26
+// a lazy decision.
24 27
 
25
-// AnalyticsAdapter
26
-function AnalyticsAdapter() {
27
-    // XXX Since we asynchronously load the integration of the analytics API and
28
-    // the analytics API may asynchronously load its implementation (e.g. Google
29
-    // Analytics), we cannot make the decision with respect to which analytics
30
-    // implementation we will use here and we have to postpone it i.e. we will
31
-    // make a lazy decision.
32
-}
28
+class AnalyticsAdapter {
29
+    constructor () {
30
+    }
33 31
 
34
-AnalyticsAdapter.prototype.sendEvent = function (action, data) {
35
-  var a = this.analytics;
32
+    sendEvent (...args) {
33
+        var a = this.analytics;
36 34
 
37
-  if (a === null || typeof a === 'undefined') {
38
-      var AnalyticsImpl = window.Analytics || NoopAnalytics;
35
+        if (a === null || typeof a === 'undefined') {
36
+            var AnalyticsImpl = window.Analytics || NoopAnalytics;
39 37
 
40
-      this.analytics = a = new AnalyticsImpl();
41
-  }
42
-  try {
43
-      a.sendEvent.apply(a, arguments);
44
-  } catch (ignored) {}
45
-};
38
+            this.analytics = a = new AnalyticsImpl();
39
+        }
40
+        try {
41
+            a.sendEvent(...args);
42
+        } catch (ignored) {}
43
+    }
44
+}
46 45
 
47
-module.exports = new AnalyticsAdapter();
46
+export default new AnalyticsAdapter();

+ 0
- 273
modules/statistics/CallStats.js 查看文件

@@ -1,273 +0,0 @@
1
-/* global config, $, APP, Strophe, callstats */
2
-
3
-var Settings = require('../settings/Settings');
4
-var ScriptUtil = require('../util/ScriptUtil');
5
-var jsSHA = require('jssha');
6
-var io = require('socket.io-client');
7
-var callStats = null;
8
-
9
-/**
10
- * @const
11
- * @see http://www.callstats.io/api/#enumeration-of-wrtcfuncnames
12
- */
13
-var wrtcFuncNames = {
14
-    createOffer:          "createOffer",
15
-    createAnswer:         "createAnswer",
16
-    setLocalDescription:  "setLocalDescription",
17
-    setRemoteDescription: "setRemoteDescription",
18
-    addIceCandidate:      "addIceCandidate",
19
-    getUserMedia:         "getUserMedia"
20
-};
21
-
22
-/**
23
- * Some errors may occur before CallStats.init in which case we will accumulate 
24
- * them and submit them to callstats.io on CallStats.init.
25
- */
26
-var pendingErrors = [];
27
-
28
-function initCallback (err, msg) {
29
-    console.log("CallStats Status: err=" + err + " msg=" + msg);
30
-}
31
-
32
-/**
33
- * The indicator which determines whether the integration of callstats.io is
34
- * enabled/allowed. Its value does not indicate whether the integration will
35
- * succeed at runtime but rather whether it is to be attempted at runtime at
36
- * all.
37
- */
38
-var _enabled
39
-    = config.callStatsID && config.callStatsSecret
40
-        // Even though AppID and AppSecret may be specified, the integration of
41
-        // callstats.io may be disabled because of globally-disallowed requests
42
-        // to any third parties.
43
-        && (config.disableThirdPartyRequests !== true);
44
-
45
-if (_enabled) {
46
-    // Since callstats.io is a third party, we cannot guarantee the quality of
47
-    // their service. More specifically, their server may take noticeably long
48
-    // time to respond. Consequently, it is in our best interest (in the sense
49
-    // that the intergration of callstats.io is pretty important to us but not
50
-    // enough to allow it to prevent people from joining a conference) to (1)
51
-    // start downloading their API as soon as possible and (2) do the
52
-    // downloading asynchronously.
53
-    ScriptUtil.loadScript(
54
-            'https://api.callstats.io/static/callstats.min.js',
55
-            /* async */ true,
56
-            /* prepend */ true);
57
-    // FIXME At the time of this writing, we hope that the callstats.io API will
58
-    // have loaded by the time we needed it (i.e. CallStats.init is invoked).
59
-}
60
-
61
-/**
62
- * Returns a function which invokes f in a try/catch block, logs any exception
63
- * to the console, and then swallows it.
64
- *
65
- * @param f the function to invoke in a try/catch block
66
- * @return a function which invokes f in a try/catch block, logs any exception
67
- * to the console, and then swallows it
68
- */
69
-function _try_catch (f) {
70
-    return function () {
71
-        try {
72
-            f.apply(this, arguments);
73
-        } catch (e) {
74
-            console.error(e);
75
-        }
76
-    };
77
-}
78
-
79
-var CallStats = {
80
-    init: _try_catch(function (jingleSession) {
81
-        if(!this.isEnabled() || callStats !== null) {
82
-            return;
83
-        }
84
-
85
-        try {
86
-            callStats = new callstats($, io, jsSHA);
87
-
88
-            this.session = jingleSession;
89
-            this.peerconnection = jingleSession.peerconnection.peerconnection;
90
-            this.userID = Settings.getCallStatsUserName();
91
-
92
-            var location = window.location;
93
-
94
-            this.confID = location.hostname + location.pathname;
95
-
96
-            callStats.initialize(
97
-                    config.callStatsID, config.callStatsSecret,
98
-                    this.userID /* generated or given by the origin server */,
99
-                    initCallback);
100
-
101
-            var usage = callStats.fabricUsage.multiplex;
102
-
103
-            callStats.addNewFabric(
104
-                    this.peerconnection,
105
-                    Strophe.getResourceFromJid(jingleSession.peerjid),
106
-                    usage,
107
-                    this.confID,
108
-                    this.pcCallback.bind(this));
109
-        } catch (e) {
110
-            // The callstats.io API failed to initialize (e.g. because its
111
-            // download failed to succeed in general or on time). Further
112
-            // attempts to utilize it cannot possibly succeed.
113
-            callStats = null;
114
-            console.error(e);
115
-        }
116
-        // Notify callstats about pre-init failures if there were any.
117
-        if (callStats && pendingErrors.length) {
118
-            pendingErrors.forEach(function (error) {
119
-                this._reportError(error.type, error.error, error.pc);
120
-            }, this);
121
-            pendingErrors.length = 0;
122
-        }
123
-    }),
124
-
125
-    /**
126
-     * Returns true if the callstats integration is enabled, otherwise returns
127
-     * false.
128
-     *
129
-     * @returns true if the callstats integration is enabled, otherwise returns
130
-     * false.
131
-     */
132
-    isEnabled: function() {
133
-        return _enabled;
134
-    },
135
-
136
-    pcCallback: _try_catch(function (err, msg) {
137
-        if (!callStats) {
138
-            return;
139
-        }
140
-        console.log("Monitoring status: "+ err + " msg: " + msg);
141
-        callStats.sendFabricEvent(this.peerconnection,
142
-            callStats.fabricEvent.fabricSetup, this.confID);
143
-    }),
144
-
145
-    sendMuteEvent: _try_catch(function (mute, type) {
146
-        if (!callStats) {
147
-            return;
148
-        }
149
-        var event = null;
150
-        if (type === "video") {
151
-            event = (mute? callStats.fabricEvent.videoPause :
152
-                callStats.fabricEvent.videoResume);
153
-        }
154
-        else {
155
-            event = (mute? callStats.fabricEvent.audioMute :
156
-                callStats.fabricEvent.audioUnmute);
157
-        }
158
-        callStats.sendFabricEvent(this.peerconnection, event, this.confID);
159
-    }),
160
-
161
-    sendTerminateEvent: _try_catch(function () {
162
-        if(!callStats) {
163
-            return;
164
-        }
165
-        callStats.sendFabricEvent(this.peerconnection,
166
-            callStats.fabricEvent.fabricTerminated, this.confID);
167
-    }),
168
-
169
-    sendSetupFailedEvent: _try_catch(function () {
170
-        if(!callStats) {
171
-            return;
172
-        }
173
-        callStats.sendFabricEvent(this.peerconnection,
174
-            callStats.fabricEvent.fabricSetupFailed, this.confID);
175
-    }),
176
-
177
-    /**
178
-     * Sends the given feedback through CallStats.
179
-     *
180
-     * @param overallFeedback an integer between 1 and 5 indicating the
181
-     * user feedback
182
-     * @param detailedFeedback detailed feedback from the user. Not yet used
183
-     */
184
-    sendFeedback: _try_catch(function(overallFeedback, detailedFeedback) {
185
-        if(!callStats) {
186
-            return;
187
-        }
188
-        var feedbackString =    '{"userID":"' + this.userID + '"' +
189
-                                ', "overall":' + overallFeedback +
190
-                                ', "comment": "' + detailedFeedback + '"}';
191
-
192
-        var feedbackJSON = JSON.parse(feedbackString);
193
-
194
-        callStats.sendUserFeedback(this.confID, feedbackJSON);
195
-    }),
196
-
197
-    /**
198
-     * Reports an error to callstats.
199
-     *
200
-     * @param type the type of the error, which will be one of the wrtcFuncNames
201
-     * @param e the error
202
-     * @param pc the peerconnection
203
-     * @private
204
-     */
205
-    _reportError: function (type, e, pc) {
206
-        if (callStats) {
207
-            callStats.reportError(pc, this.confID, type, e);
208
-        } else if (this.isEnabled()) {
209
-            pendingErrors.push({ type: type, error: e, pc: pc });
210
-        }
211
-        // else just ignore it
212
-    },
213
-
214
-    /**
215
-     * Notifies CallStats that getUserMedia failed.
216
-     *
217
-     * @param {Error} e error to send
218
-     */
219
-    sendGetUserMediaFailed: _try_catch(function (e) {
220
-        this._reportError(wrtcFuncNames.getUserMedia, e, null);
221
-    }),
222
-
223
-    /**
224
-     * Notifies CallStats that peer connection failed to create offer.
225
-     *
226
-     * @param {Error} e error to send
227
-     * @param {RTCPeerConnection} pc connection on which failure occured.
228
-     */
229
-    sendCreateOfferFailed: _try_catch(function (e, pc) {
230
-        this._reportError(wrtcFuncNames.createOffer, e, pc);
231
-    }),
232
-
233
-    /**
234
-     * Notifies CallStats that peer connection failed to create answer.
235
-     *
236
-     * @param {Error} e error to send
237
-     * @param {RTCPeerConnection} pc connection on which failure occured.
238
-     */
239
-    sendCreateAnswerFailed: _try_catch(function (e, pc) {
240
-        this._reportError(wrtcFuncNames.createAnswer, e, pc);
241
-    }),
242
-
243
-    /**
244
-     * Notifies CallStats that peer connection failed to set local description.
245
-     *
246
-     * @param {Error} e error to send
247
-     * @param {RTCPeerConnection} pc connection on which failure occured.
248
-     */
249
-    sendSetLocalDescFailed: _try_catch(function (e, pc) {
250
-        this._reportError(wrtcFuncNames.setLocalDescription, e, pc);
251
-    }),
252
-
253
-    /**
254
-     * Notifies CallStats that peer connection failed to set remote description.
255
-     *
256
-     * @param {Error} e error to send
257
-     * @param {RTCPeerConnection} pc connection on which failure occured.
258
-     */
259
-    sendSetRemoteDescFailed: _try_catch(function (e, pc) {
260
-        this._reportError(wrtcFuncNames.setRemoteDescription, e, pc);
261
-    }),
262
-
263
-    /**
264
-     * Notifies CallStats that peer connection failed to add ICE candidate.
265
-     *
266
-     * @param {Error} e error to send
267
-     * @param {RTCPeerConnection} pc connection on which failure occured.
268
-     */
269
-    sendAddIceCandidateFailed: _try_catch(function (e, pc) {
270
-        this._reportError(wrtcFuncNames.addIceCandidate, e, pc);
271
-    })
272
-};
273
-module.exports = CallStats;

+ 0
- 128
modules/statistics/LocalStatsCollector.js 查看文件

@@ -1,128 +0,0 @@
1
-/* global config, AudioContext */
2
-/**
3
- * Provides statistics for the local stream.
4
- */
5
-
6
-var RTCBrowserType = require('../RTC/RTCBrowserType');
7
-var StatisticsEvents = require('../../service/statistics/Events');
8
-
9
-/**
10
- * Size of the webaudio analyzer buffer.
11
- * @type {number}
12
- */
13
-var WEBAUDIO_ANALYZER_FFT_SIZE = 2048;
14
-
15
-/**
16
- * Value of the webaudio analyzer smoothing time parameter.
17
- * @type {number}
18
- */
19
-var WEBAUDIO_ANALYZER_SMOOTING_TIME = 0.8;
20
-
21
-/**
22
- * Converts time domain data array to audio level.
23
- * @param samples the time domain data array.
24
- * @returns {number} the audio level
25
- */
26
-function timeDomainDataToAudioLevel(samples) {
27
-
28
-    var maxVolume = 0;
29
-
30
-    var length = samples.length;
31
-
32
-    for (var i = 0; i < length; i++) {
33
-        if (maxVolume < samples[i])
34
-            maxVolume = samples[i];
35
-    }
36
-
37
-    return parseFloat(((maxVolume - 127) / 128).toFixed(3));
38
-}
39
-
40
-/**
41
- * Animates audio level change
42
- * @param newLevel the new audio level
43
- * @param lastLevel the last audio level
44
- * @returns {Number} the audio level to be set
45
- */
46
-function animateLevel(newLevel, lastLevel) {
47
-    var value = 0;
48
-    var diff = lastLevel - newLevel;
49
-    if(diff > 0.2) {
50
-        value = lastLevel - 0.2;
51
-    }
52
-    else if(diff < -0.4) {
53
-        value = lastLevel + 0.4;
54
-    }
55
-    else {
56
-        value = newLevel;
57
-    }
58
-
59
-    return parseFloat(value.toFixed(3));
60
-}
61
-
62
-
63
-/**
64
- * <tt>LocalStatsCollector</tt> calculates statistics for the local stream.
65
- *
66
- * @param stream the local stream
67
- * @param interval stats refresh interval given in ms.
68
- * @constructor
69
- */
70
-function LocalStatsCollector(stream, interval,
71
-                             statisticsService, eventEmitter) {
72
-    window.AudioContext = window.AudioContext || window.webkitAudioContext;
73
-    this.stream = stream;
74
-    this.intervalId = null;
75
-    this.intervalMilis = interval;
76
-    this.eventEmitter = eventEmitter;
77
-    this.audioLevel = 0;
78
-    this.statisticsService = statisticsService;
79
-}
80
-
81
-/**
82
- * Starts the collecting the statistics.
83
- */
84
-LocalStatsCollector.prototype.start = function () {
85
-    if (config.disableAudioLevels || !window.AudioContext ||
86
-        RTCBrowserType.isTemasysPluginUsed())
87
-        return;
88
-
89
-    var context = new AudioContext();
90
-    var analyser = context.createAnalyser();
91
-    analyser.smoothingTimeConstant = WEBAUDIO_ANALYZER_SMOOTING_TIME;
92
-    analyser.fftSize = WEBAUDIO_ANALYZER_FFT_SIZE;
93
-
94
-
95
-    var source = context.createMediaStreamSource(this.stream);
96
-    source.connect(analyser);
97
-
98
-
99
-    var self = this;
100
-
101
-    this.intervalId = setInterval(
102
-        function () {
103
-            var array = new Uint8Array(analyser.frequencyBinCount);
104
-            analyser.getByteTimeDomainData(array);
105
-            var audioLevel = timeDomainDataToAudioLevel(array);
106
-            if (audioLevel != self.audioLevel) {
107
-                self.audioLevel = animateLevel(audioLevel, self.audioLevel);
108
-                self.eventEmitter.emit(
109
-                    StatisticsEvents.AUDIO_LEVEL,
110
-                    self.statisticsService.LOCAL_JID,
111
-                    self.audioLevel);
112
-            }
113
-        },
114
-        this.intervalMilis
115
-    );
116
-};
117
-
118
-/**
119
- * Stops collecting the statistics.
120
- */
121
-LocalStatsCollector.prototype.stop = function () {
122
-    if (this.intervalId) {
123
-        clearInterval(this.intervalId);
124
-        this.intervalId = null;
125
-    }
126
-};
127
-
128
-module.exports = LocalStatsCollector;

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

@@ -146,7 +146,6 @@ function StatsCollector(peerconnection, audioLevelsInterval, statsInterval, even
146 146
 {
147 147
     this.peerconnection = peerconnection;
148 148
     this.baselineAudioLevelsReport = null;
149
-    this.currentAudioLevelsReport = null;
150 149
     this.currentStatsReport = null;
151 150
     this.baselineStatsReport = null;
152 151
     this.audioLevelsIntervalId = null;
@@ -252,10 +251,7 @@ StatsCollector.prototype.start = function ()
252 251
                             results = report.result();
253 252
                         }
254 253
                         //console.error("Got interval report", results);
255
-                        self.currentAudioLevelsReport = results;
256
-                        self.processAudioLevelReport();
257
-                        self.baselineAudioLevelsReport =
258
-                            self.currentAudioLevelsReport;
254
+                        self.baselineAudioLevelsReport = results;
259 255
                     },
260 256
                     self.errorCallback
261 257
                 );
@@ -385,7 +381,7 @@ StatsCollector.prototype.addStatsToBeLogged = function (reports) {
385 381
 
386 382
 StatsCollector.prototype.logStats = function () {
387 383
 
388
-    if(!APP.xmpp.sendLogs(this.statsToBeLogged))
384
+    if(!APP.conference._room.xmpp.sendLogs(this.statsToBeLogged))
389 385
         return;
390 386
     // Reset the stats
391 387
     this.statsToBeLogged.stats = {};
@@ -501,7 +497,7 @@ StatsCollector.prototype.processStatsReport = function () {
501 497
         var ssrc = getStatValue(now, 'ssrc');
502 498
         if(!ssrc)
503 499
             continue;
504
-        var jid = APP.xmpp.getJidFromSSRC(ssrc);
500
+        var jid = APP.conference._room.room.getJidBySSRC(ssrc);
505 501
         if (!jid && (Date.now() - now.timestamp) < 3000) {
506 502
             console.warn("No jid for ssrc: " + ssrc);
507 503
             continue;
@@ -647,76 +643,22 @@ StatsCollector.prototype.processStatsReport = function () {
647 643
         upload:
648 644
             calculatePacketLoss(lostPackets.upload, totalPackets.upload)
649 645
     };
646
+
647
+    let idResolution = {};
648
+    if (resolutions) { // use id instead of jid
649
+        Object.keys(resolutions).forEach(function (jid) {
650
+            let id = Strophe.getResourceFromJid(jid);
651
+            idResolution[id] = resolutions[jid];
652
+        });
653
+    }
650 654
     this.eventEmitter.emit(StatisticsEvents.CONNECTION_STATS,
651 655
         {
652 656
             "bitrate": PeerStats.bitrate,
653 657
             "packetLoss": PeerStats.packetLoss,
654 658
             "bandwidth": PeerStats.bandwidth,
655
-            "resolution": resolutions,
659
+            "resolution": idResolution,
656 660
             "transport": PeerStats.transport
657 661
         });
658 662
     PeerStats.transport = [];
659 663
 
660 664
 };
661
-
662
-/**
663
- * Stats processing logic.
664
- */
665
-StatsCollector.prototype.processAudioLevelReport = function () {
666
-    if (!this.baselineAudioLevelsReport) {
667
-        return;
668
-    }
669
-
670
-    for (var idx in this.currentAudioLevelsReport) {
671
-        var now = this.currentAudioLevelsReport[idx];
672
-
673
-        if (now.type != 'ssrc') {
674
-            continue;
675
-        }
676
-
677
-        var before = this.baselineAudioLevelsReport[idx];
678
-        if (!before) {
679
-            console.warn(getStatValue(now, 'ssrc') + ' not enough data');
680
-            continue;
681
-        }
682
-
683
-        var ssrc = getStatValue(now, 'ssrc');
684
-        var jid = APP.xmpp.getJidFromSSRC(ssrc);
685
-        if (!jid) {
686
-            if((Date.now() - now.timestamp) < 3000)
687
-                console.warn("No jid for ssrc: " + ssrc);
688
-            continue;
689
-        }
690
-
691
-        var jidStats = this.jid2stats[jid];
692
-        if (!jidStats) {
693
-            jidStats = new PeerStats();
694
-            this.jid2stats[jid] = jidStats;
695
-        }
696
-
697
-        // Audio level
698
-        var audioLevel = null;
699
-
700
-        try {
701
-            audioLevel = getStatValue(now, 'audioInputLevel');
702
-            if (!audioLevel)
703
-                audioLevel = getStatValue(now, 'audioOutputLevel');
704
-        }
705
-        catch(e) {/*not supported*/
706
-            console.warn("Audio Levels are not available in the statistics.");
707
-            clearInterval(this.audioLevelsIntervalId);
708
-            return;
709
-        }
710
-
711
-        if (audioLevel) {
712
-            // TODO: can't find specs about what this value really is,
713
-            // but it seems to vary between 0 and around 32k.
714
-            audioLevel = audioLevel / 32767;
715
-            jidStats.setSsrcAudioLevel(ssrc, audioLevel);
716
-            if (jid != APP.xmpp.myJid()) {
717
-                this.eventEmitter.emit(
718
-                    StatisticsEvents.AUDIO_LEVEL, jid, audioLevel);
719
-            }
720
-        }
721
-    }
722
-};

+ 9
- 100
modules/statistics/statistics.js 查看文件

@@ -2,28 +2,17 @@
2 2
 /**
3 3
  * Created by hristo on 8/4/14.
4 4
  */
5
-var LocalStats = require("./LocalStatsCollector.js");
6 5
 var RTPStats = require("./RTPStatsCollector.js");
7 6
 var EventEmitter = require("events");
8 7
 var StreamEventTypes = require("../../service/RTC/StreamEventTypes.js");
9 8
 var XMPPEvents = require("../../service/xmpp/XMPPEvents");
10
-var CallStats = require("./CallStats");
11 9
 var RTCEvents = require("../../service/RTC/RTCEvents");
12 10
 var StatisticsEvents = require("../../service/statistics/Events");
13 11
 
14 12
 var eventEmitter = new EventEmitter();
15 13
 
16
-var localStats = null;
17
-
18 14
 var rtpStats = null;
19 15
 
20
-function stopLocal() {
21
-    if (localStats) {
22
-        localStats.stop();
23
-        localStats = null;
24
-    }
25
-}
26
-
27 16
 function stopRemote() {
28 17
     if (rtpStats) {
29 18
         rtpStats.stop();
@@ -41,26 +30,14 @@ function startRemoteStats (peerconnection) {
41 30
     rtpStats.start();
42 31
 }
43 32
 
44
-function onStreamCreated(stream) {
45
-    if(stream.getOriginalStream().getAudioTracks().length === 0) {
46
-        return;
47
-    }
48
-
49
-    localStats = new LocalStats(stream.getOriginalStream(), 200, statistics,
50
-        eventEmitter);
51
-    localStats.start();
52
-}
53
-
54 33
 function onDisposeConference(onUnload) {
55
-    CallStats.sendTerminateEvent();
56 34
     stopRemote();
57
-    if(onUnload) {
58
-        stopLocal();
35
+    if (onUnload) {
59 36
         eventEmitter.removeAllListeners();
60 37
     }
61 38
 }
62 39
 
63
-var statistics = {
40
+export default {
64 41
     /**
65 42
      * Indicates that this audio level is for local jid.
66 43
      * @type {string}
@@ -74,89 +51,21 @@ var statistics = {
74 51
         eventEmitter.removeListener(type, listener);
75 52
     },
76 53
     stop: function () {
77
-        stopLocal();
78 54
         stopRemote();
79
-        if(eventEmitter)
80
-        {
55
+        if (eventEmitter) {
81 56
             eventEmitter.removeAllListeners();
82 57
         }
83 58
     },
84
-    stopRemoteStatistics: function()
85
-    {
86
-        stopRemote();
87
-    },
88 59
     start: function () {
89
-        APP.RTC.addStreamListener(onStreamCreated,
90
-            StreamEventTypes.EVENT_TYPE_LOCAL_CREATED);
91
-        APP.xmpp.addListener(XMPPEvents.DISPOSE_CONFERENCE,
92
-                             onDisposeConference);
60
+        const xmpp = APP.conference._room.xmpp;
61
+        xmpp.addListener(
62
+            XMPPEvents.DISPOSE_CONFERENCE,
63
+            onDisposeConference
64
+        );
93 65
         //FIXME: we may want to change CALL INCOMING event to
94 66
         // onnegotiationneeded
95
-        APP.xmpp.addListener(XMPPEvents.CALL_INCOMING, function (event) {
67
+        xmpp.addListener(XMPPEvents.CALL_INCOMING, function (event) {
96 68
             startRemoteStats(event.peerconnection);
97
-//            CallStats.init(event);
98
-        });
99
-        APP.xmpp.addListener(XMPPEvents.PEERCONNECTION_READY,
100
-            function (session) {
101
-            CallStats.init(session);
102 69
         });
103
-        APP.RTC.addListener(RTCEvents.AUDIO_MUTE, function (mute) {
104
-            CallStats.sendMuteEvent(mute, "audio");
105
-        });
106
-        APP.xmpp.addListener(XMPPEvents.CONFERENCE_SETUP_FAILED, function () {
107
-            CallStats.sendSetupFailedEvent();
108
-        });
109
-        APP.RTC.addListener(RTCEvents.VIDEO_MUTE, function (mute) {
110
-            CallStats.sendMuteEvent(mute, "video");
111
-        });
112
-
113
-        APP.RTC.addListener(RTCEvents.GET_USER_MEDIA_FAILED, function (e) {
114
-            CallStats.sendGetUserMediaFailed(e);
115
-        });
116
-        APP.xmpp.addListener(RTCEvents.CREATE_OFFER_FAILED, function (e, pc) {
117
-            CallStats.sendCreateOfferFailed(e, pc);
118
-        });
119
-        APP.xmpp.addListener(RTCEvents.CREATE_ANSWER_FAILED, function (e, pc) {
120
-            CallStats.sendCreateAnswerFailed(e, pc);
121
-        });
122
-        APP.xmpp.addListener(
123
-            RTCEvents.SET_LOCAL_DESCRIPTION_FAILED,
124
-            function (e, pc) {
125
-                CallStats.sendSetLocalDescFailed(e, pc);
126
-            }
127
-        );
128
-        APP.xmpp.addListener(
129
-            RTCEvents.SET_REMOTE_DESCRIPTION_FAILED,
130
-            function (e, pc) {
131
-                CallStats.sendSetRemoteDescFailed(e, pc);
132
-            }
133
-        );
134
-        APP.xmpp.addListener(
135
-            RTCEvents.ADD_ICE_CANDIDATE_FAILED,
136
-            function (e, pc) {
137
-                CallStats.sendAddIceCandidateFailed(e, pc);
138
-            }
139
-        );
140
-    },
141
-    /**
142
-     * Obtains audio level reported in the stats for specified peer.
143
-     * @param peerJid full MUC jid of the user for whom we want to obtain last
144
-     *        audio level.
145
-     * @param ssrc the SSRC of audio stream for which we want to obtain audio
146
-     *        level.
147
-     * @returns {*} a float form 0 to 1 that represents current audio level or
148
-     *              <tt>null</tt> if for any reason the value is not available
149
-     *              at this time.
150
-     */
151
-    getPeerSSRCAudioLevel: function (peerJid, ssrc) {
152
-
153
-        var peerStats = rtpStats.jid2stats[peerJid];
154
-
155
-        return peerStats ? peerStats.ssrc2AudioLevel[ssrc] : null;
156 70
     }
157 71
 };
158
-
159
-
160
-
161
-
162
-module.exports = statistics;

+ 6
- 24
modules/translation/translation.js 查看文件

@@ -1,7 +1,6 @@
1 1
 /* global $, require, config, interfaceConfig */
2 2
 var i18n = require("i18next-client");
3 3
 var languages = require("../../service/translation/languages");
4
-var Settings = require("../settings/Settings");
5 4
 var DEFAULT_LANG = languages.EN;
6 5
 
7 6
 i18n.addPostProcessor("resolveAppName", function(value, key, options) {
@@ -68,7 +67,7 @@ function initCompleted(t) {
68 67
     $("[data-i18n]").i18n();
69 68
 }
70 69
 
71
-function checkForParameter() {
70
+function getLangFromQuery() {
72 71
     var query = window.location.search.substring(1);
73 72
     var vars = query.split("&");
74 73
     for (var i=0;i<vars.length;i++) {
@@ -82,27 +81,11 @@ function checkForParameter() {
82 81
 }
83 82
 
84 83
 module.exports = {
85
-    init: function (lang) {
86
-        var options = defaultOptions;
84
+    init: function (settingsLang) {
85
+        let options = defaultOptions;
87 86
 
88
-
89
-        if(!lang)
90
-        {
91
-            lang = checkForParameter();
92
-            if(!lang)
93
-            {
94
-                var settings = Settings.getSettings();
95
-                if(settings)
96
-                    lang = settings.language;
97
-
98
-                if(!lang && config.defaultLanguage)
99
-                {
100
-                    lang = config.defaultLanguage;
101
-                }
102
-            }
103
-        }
104
-
105
-        if(lang) {
87
+        let lang = getLangFromQuery() || settingsLang || config.defaultLanguage;
88
+        if (lang) {
106 89
             options.lng = lang;
107 90
         }
108 91
 
@@ -124,8 +107,7 @@ module.exports = {
124 107
     },
125 108
     generateTranslationHTML: function (key, options) {
126 109
         var str = "<span data-i18n=\"" + key + "\"";
127
-        if(options)
128
-        {
110
+        if (options) {
129 111
             str += " data-i18n-options=\"" + JSON.stringify(options) + "\"";
130 112
         }
131 113
         str += ">";

+ 0
- 32
modules/util/ScriptUtil.js 查看文件

@@ -1,32 +0,0 @@
1
-/**
2
- * Implements utility functions which facilitate the dealing with scripts such
3
- * as the download and execution of a JavaScript file.
4
- */
5
-var ScriptUtil = {
6
-    /**
7
-     * Loads a script from a specific source.
8
-     *
9
-     * @param src the source from the which the script is to be (down)loaded
10
-     * @param async true to asynchronously load the script or false to
11
-     * synchronously load the script
12
-     * @param prepend true to schedule the loading of the script as soon as
13
-     * possible or false to schedule the loading of the script at the end of the
14
-     * scripts known at the time
15
-     */
16
-    loadScript: function (src, async, prepend) {
17
-        var d = document;
18
-        var tagName = 'script';
19
-        var script = d.createElement(tagName);
20
-        var referenceNode = d.getElementsByTagName(tagName)[0];
21
-
22
-        script.async = async;
23
-        script.src = src;
24
-        if (prepend) {
25
-            referenceNode.parentNode.insertBefore(script, referenceNode);
26
-        } else {
27
-            referenceNode.parentNode.appendChild(script);
28
-        }
29
-    },
30
-};
31
-
32
-module.exports = ScriptUtil;

+ 2
- 6
modules/util/UsernameGenerator.js 查看文件

@@ -1,4 +1,4 @@
1
-var RandomUtil = require('./RandomUtil');
1
+import RandomUtil from './RandomUtil';
2 2
 
3 3
 /**
4 4
  * from faker.js - Copyright (c) 2014-2015 Matthew Bergman & Marak Squires
@@ -417,13 +417,9 @@ var names = [
417 417
  * Generate random username.
418 418
  * @returns {string} random username
419 419
  */
420
-function generateUsername () {
420
+export function generateUsername () {
421 421
   var name = RandomUtil.randomElement(names);
422 422
   var suffix = RandomUtil.randomAlphanumStr(3);
423 423
 
424 424
   return name + '-' +  suffix;
425 425
 }
426
-
427
-module.exports = {
428
-  generateUsername: generateUsername
429
-};

+ 14
- 0
modules/util/helpers.js 查看文件

@@ -0,0 +1,14 @@
1
+/**
2
+ * Create deferred object.
3
+ * @returns {{promise, resolve, reject}}
4
+ */
5
+export function createDeferred () {
6
+    let deferred = {};
7
+
8
+    deferred.promise = new Promise(function (resolve, reject) {
9
+        deferred.resolve = resolve;
10
+        deferred.reject = reject;
11
+    });
12
+
13
+    return deferred;
14
+}

+ 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
- 1521
modules/xmpp/JingleSessionPC.js
文件差异内容过多而无法显示
查看文件


+ 0
- 643
modules/xmpp/SDP.js 查看文件

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

+ 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
- 362
modules/xmpp/SDPUtil.js 查看文件

@@ -1,362 +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 += protocol; //.toUpperCase(); // chrome M23 doesn't like this
324
-        line += ' ';
325
-        line += cand.getAttribute('priority');
326
-        line += ' ';
327
-        line += cand.getAttribute('ip');
328
-        line += ' ';
329
-        line += cand.getAttribute('port');
330
-        line += ' ';
331
-        line += 'typ';
332
-        line += ' ' + cand.getAttribute('type');
333
-        line += ' ';
334
-        switch (cand.getAttribute('type')) {
335
-            case 'srflx':
336
-            case 'prflx':
337
-            case 'relay':
338
-                if (cand.getAttribute('rel-addr') && cand.getAttribute('rel-port')) {
339
-                    line += 'raddr';
340
-                    line += ' ';
341
-                    line += cand.getAttribute('rel-addr');
342
-                    line += ' ';
343
-                    line += 'rport';
344
-                    line += ' ';
345
-                    line += cand.getAttribute('rel-port');
346
-                    line += ' ';
347
-                }
348
-                break;
349
-        }
350
-        if (protocol.toLowerCase() == 'tcp') {
351
-            line += 'tcptype';
352
-            line += ' ';
353
-            line += cand.getAttribute('tcptype');
354
-            line += ' ';
355
-        }
356
-        line += 'generation';
357
-        line += ' ';
358
-        line += cand.getAttribute('generation') || '0';
359
-        return line + '\r\n';
360
-    }
361
-};
362
-module.exports = SDPUtil;

+ 0
- 492
modules/xmpp/TraceablePeerConnection.js 查看文件

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

+ 0
- 442
modules/xmpp/moderator.js 查看文件

@@ -1,442 +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
-
153
-        if (config.enforcedBridge) {
154
-            elem.c(
155
-                'property',
156
-                { name: 'enforcedBridge', value: config.enforcedBridge})
157
-                .up();
158
-        }
159
-
160
-        // Tell the focus we have Jigasi configured
161
-        if (config.hosts.call_control !== undefined) {
162
-            elem.c(
163
-                'property',
164
-                { name: 'call_control', value: config.hosts.call_control})
165
-                .up();
166
-        }
167
-        if (config.channelLastN !== undefined) {
168
-            elem.c(
169
-                'property',
170
-                { name: 'channelLastN', value: config.channelLastN})
171
-                .up();
172
-        }
173
-        if (config.adaptiveLastN !== undefined) {
174
-            elem.c(
175
-                'property',
176
-                { name: 'adaptiveLastN', value: config.adaptiveLastN})
177
-                .up();
178
-        }
179
-        if (config.adaptiveSimulcast !== undefined) {
180
-            elem.c(
181
-                'property',
182
-                { name: 'adaptiveSimulcast', value: config.adaptiveSimulcast})
183
-                .up();
184
-        }
185
-        if (config.openSctp !== undefined) {
186
-            elem.c(
187
-                'property',
188
-                { name: 'openSctp', value: config.openSctp})
189
-                .up();
190
-        }
191
-        if(config.startAudioMuted !== undefined)
192
-        {
193
-            elem.c(
194
-                'property',
195
-                { name: 'startAudioMuted', value: config.startAudioMuted})
196
-                .up();
197
-        }
198
-        if(config.startVideoMuted !== undefined)
199
-        {
200
-            elem.c(
201
-                'property',
202
-                { name: 'startVideoMuted', value: config.startVideoMuted})
203
-                .up();
204
-        }
205
-        elem.c(
206
-            'property',
207
-            { name: 'simulcastMode', value: 'rewriting'})
208
-            .up();
209
-        elem.up();
210
-        return elem;
211
-    },
212
-
213
-    parseSessionId: function (resultIq) {
214
-        var sessionId = $(resultIq).find('conference').attr('session-id');
215
-        if (sessionId) {
216
-            console.info('Received sessionId: ' + sessionId);
217
-            localStorage.setItem('sessionId', sessionId);
218
-        }
219
-    },
220
-
221
-    parseConfigOptions: function (resultIq) {
222
-
223
-        Moderator.setFocusUserJid(
224
-            $(resultIq).find('conference').attr('focusjid'));
225
-
226
-        var authenticationEnabled
227
-            = $(resultIq).find(
228
-                '>conference>property' +
229
-                '[name=\'authentication\'][value=\'true\']').length > 0;
230
-
231
-        console.info("Authentication enabled: " + authenticationEnabled);
232
-
233
-        externalAuthEnabled = $(resultIq).find(
234
-                '>conference>property' +
235
-                '[name=\'externalAuth\'][value=\'true\']').length > 0;
236
-
237
-        console.info('External authentication enabled: ' + externalAuthEnabled);
238
-
239
-        if (!externalAuthEnabled) {
240
-            // We expect to receive sessionId in 'internal' authentication mode
241
-            Moderator.parseSessionId(resultIq);
242
-        }
243
-
244
-        var authIdentity = $(resultIq).find('>conference').attr('identity');
245
-
246
-        eventEmitter.emit(AuthenticationEvents.IDENTITY_UPDATED,
247
-            authenticationEnabled, authIdentity);
248
-    
249
-        // Check if focus has auto-detected Jigasi component(this will be also
250
-        // included if we have passed our host from the config)
251
-        if ($(resultIq).find(
252
-            '>conference>property' +
253
-            '[name=\'sipGatewayEnabled\'][value=\'true\']').length) {
254
-            sipGatewayEnabled = true;
255
-        }
256
-    
257
-        console.info("Sip gateway enabled: " + sipGatewayEnabled);
258
-    },
259
-
260
-    // FIXME: we need to show the fact that we're waiting for the focus
261
-    // to the user(or that focus is not available)
262
-    allocateConferenceFocus: function (roomName, callback) {
263
-        // Try to use focus user JID from the config
264
-        Moderator.setFocusUserJid(config.focusUserJid);
265
-        // Send create conference IQ
266
-        var iq = Moderator.createConferenceIq(roomName);
267
-        var self = this;
268
-        connection.sendIQ(
269
-            iq,
270
-            function (result) {
271
-
272
-                // Setup config options
273
-                Moderator.parseConfigOptions(result);
274
-
275
-                if ('true' === $(result).find('conference').attr('ready')) {
276
-                    // Reset both timers
277
-                    getNextTimeout(true);
278
-                    getNextErrorTimeout(true);
279
-                    // Exec callback
280
-                    callback();
281
-                } else {
282
-                    var waitMs = getNextTimeout();
283
-                    console.info("Waiting for the focus... " + waitMs);
284
-                    // Reset error timeout
285
-                    getNextErrorTimeout(true);
286
-                    window.setTimeout(
287
-                        function () {
288
-                            Moderator.allocateConferenceFocus(
289
-                                roomName, callback);
290
-                        }, waitMs);
291
-                }
292
-            },
293
-            function (error) {
294
-                // Invalid session ? remove and try again
295
-                // without session ID to get a new one
296
-                var invalidSession
297
-                    = $(error).find('>error>session-invalid').length;
298
-                if (invalidSession) {
299
-                    console.info("Session expired! - removing");
300
-                    localStorage.removeItem("sessionId");
301
-                }
302
-                if ($(error).find('>error>graceful-shutdown').length) {
303
-                    eventEmitter.emit(XMPPEvents.GRACEFUL_SHUTDOWN);
304
-                    return;
305
-                }
306
-                // Check for error returned by the reservation system
307
-                var reservationErr = $(error).find('>error>reservation-error');
308
-                if (reservationErr.length) {
309
-                    // Trigger error event
310
-                    var errorCode = reservationErr.attr('error-code');
311
-                    var errorMsg;
312
-                    if ($(error).find('>error>text')) {
313
-                        errorMsg = $(error).find('>error>text').text();
314
-                    }
315
-                    eventEmitter.emit(
316
-                        XMPPEvents.RESERVATION_ERROR, errorCode, errorMsg);
317
-                    return;
318
-                }
319
-                // Not authorized to create new room
320
-                if ($(error).find('>error>not-authorized').length) {
321
-                    console.warn("Unauthorized to start the conference", error);
322
-                    var toDomain
323
-                        = Strophe.getDomainFromJid(error.getAttribute('to'));
324
-                    if (toDomain !== config.hosts.anonymousdomain) {
325
-                        // FIXME: "is external" should come either from
326
-                        // the focus or config.js
327
-                        externalAuthEnabled = true;
328
-                    }
329
-                    eventEmitter.emit(
330
-                        XMPPEvents.AUTHENTICATION_REQUIRED,
331
-                        function () {
332
-                            Moderator.allocateConferenceFocus(
333
-                                roomName, callback);
334
-                        });
335
-                    return;
336
-                }
337
-                var waitMs = getNextErrorTimeout();
338
-                console.error("Focus error, retry after " + waitMs, error);
339
-                // Show message
340
-                var focusComponent = Moderator.getFocusComponent();
341
-                var retrySec = waitMs / 1000;
342
-                // FIXME: message is duplicated ?
343
-                // Do not show in case of session invalid
344
-                // which means just a retry
345
-                if (!invalidSession) {
346
-                    eventEmitter.emit(XMPPEvents.FOCUS_DISCONNECTED,
347
-                        focusComponent, retrySec);
348
-                }
349
-                // Reset response timeout
350
-                getNextTimeout(true);
351
-                window.setTimeout(
352
-                    function () {
353
-                        Moderator.allocateConferenceFocus(roomName, callback);
354
-                    }, waitMs);
355
-            }
356
-        );
357
-    },
358
-
359
-    getLoginUrl: function (roomName, urlCallback) {
360
-        var iq = $iq({to: Moderator.getFocusComponent(), type: 'get'});
361
-        iq.c('login-url', {
362
-            xmlns: 'http://jitsi.org/protocol/focus',
363
-            room: roomName,
364
-            'machine-uid': Settings.getSettings().uid
365
-        });
366
-        connection.sendIQ(
367
-            iq,
368
-            function (result) {
369
-                var url = $(result).find('login-url').attr('url');
370
-                url = url = decodeURIComponent(url);
371
-                if (url) {
372
-                    console.info("Got auth url: " + url);
373
-                    urlCallback(url);
374
-                } else {
375
-                    console.error(
376
-                        "Failed to get auth url from the focus", result);
377
-                }
378
-            },
379
-            function (error) {
380
-                console.error("Get auth url error", error);
381
-            }
382
-        );
383
-    },
384
-    getPopupLoginUrl: function (roomName, urlCallback) {
385
-        var iq = $iq({to: Moderator.getFocusComponent(), type: 'get'});
386
-        iq.c('login-url', {
387
-            xmlns: 'http://jitsi.org/protocol/focus',
388
-            room: roomName,
389
-            'machine-uid': Settings.getSettings().uid,
390
-            popup: true
391
-        });
392
-        connection.sendIQ(
393
-            iq,
394
-            function (result) {
395
-                var url = $(result).find('login-url').attr('url');
396
-                url = url = decodeURIComponent(url);
397
-                if (url) {
398
-                    console.info("Got POPUP auth url: " + url);
399
-                    urlCallback(url);
400
-                } else {
401
-                    console.error(
402
-                        "Failed to get POPUP auth url from the focus", result);
403
-                }
404
-            },
405
-            function (error) {
406
-                console.error('Get POPUP auth url error', error);
407
-            }
408
-        );
409
-    },
410
-    logout: function (callback) {
411
-        var iq = $iq({to: Moderator.getFocusComponent(), type: 'set'});
412
-        var sessionId = localStorage.getItem('sessionId');
413
-        if (!sessionId) {
414
-            callback();
415
-            return;
416
-        }
417
-        iq.c('logout', {
418
-            xmlns: 'http://jitsi.org/protocol/focus',
419
-            'session-id': sessionId
420
-        });
421
-        connection.sendIQ(
422
-            iq,
423
-            function (result) {
424
-                var logoutUrl = $(result).find('logout').attr('logout-url');
425
-                if (logoutUrl) {
426
-                    logoutUrl = decodeURIComponent(logoutUrl);
427
-                }
428
-                console.info("Log out OK, url: " + logoutUrl, result);
429
-                localStorage.removeItem('sessionId');
430
-                callback(logoutUrl);
431
-            },
432
-            function (error) {
433
-                console.error("Logout error", error);
434
-            }
435
-        );
436
-    }
437
-};
438
-
439
-module.exports = Moderator;
440
-
441
-
442
-

+ 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
- 702
modules/xmpp/strophe.emuc.js 查看文件

@@ -1,702 +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
-            pres.up();
566
-            this.connection.send(pres);
567
-        },
568
-        addDisplayNameToPresence: function (displayName) {
569
-            this.presMap['displayName'] = displayName;
570
-        },
571
-        // This is only for backward compatibility with clients which
572
-        // don't support getting sources from Jingle (i.e. jirecon).
573
-        addMediaToPresence: function (sourceNumber, mtype, ssrcs, direction) {
574
-            if (!this.presMap['medians'])
575
-                this.presMap['medians'] = 'http://estos.de/ns/mjs';
576
-
577
-            this.presMap['source' + sourceNumber + '_type'] = mtype;
578
-            this.presMap['source' + sourceNumber + '_ssrc'] = ssrcs;
579
-            this.presMap['source' + sourceNumber + '_direction'] = direction;
580
-        },
581
-        // This is only for backward compatibility with clients which
582
-        // don't support getting sources from Jingle (i.e. jirecon).
583
-        clearPresenceMedia: function () {
584
-            var self = this;
585
-            Object.keys(this.presMap).forEach(function (key) {
586
-                if (key.indexOf('source') != -1) {
587
-                    delete self.presMap[key];
588
-                }
589
-            });
590
-        },
591
-        addDevicesToPresence: function (devices) {
592
-            this.presMap['devices'] = devices;
593
-        },
594
-        /**
595
-         * Adds the info about the type of our video stream.
596
-         * @param videoType 'camera' or 'screen'
597
-         */
598
-        addVideoTypeToPresence: function (videoType) {
599
-            this.presMap['videoTypeNs'] = 'http://jitsi.org/jitmeet/video';
600
-            this.presMap['videoType'] = videoType;
601
-        },
602
-        addPreziToPresence: function (url, currentSlide) {
603
-            this.presMap['prezins'] = 'http://jitsi.org/jitmeet/prezi';
604
-            this.presMap['preziurl'] = url;
605
-            this.presMap['prezicurrent'] = currentSlide;
606
-        },
607
-        removePreziFromPresence: function () {
608
-            delete this.presMap['prezins'];
609
-            delete this.presMap['preziurl'];
610
-            delete this.presMap['prezicurrent'];
611
-        },
612
-        addCurrentSlideToPresence: function (currentSlide) {
613
-            this.presMap['prezicurrent'] = currentSlide;
614
-        },
615
-        getPrezi: function (roomjid) {
616
-            return this.preziMap[roomjid];
617
-        },
618
-        addAudioInfoToPresence: function (isMuted) {
619
-            this.presMap['audions'] = 'http://jitsi.org/jitmeet/audio';
620
-            this.presMap['audiomuted'] = isMuted.toString();
621
-        },
622
-        addVideoInfoToPresence: function (isMuted) {
623
-            this.presMap['videons'] = 'http://jitsi.org/jitmeet/video';
624
-            this.presMap['videomuted'] = isMuted.toString();
625
-        },
626
-        addConnectionInfoToPresence: function (stats) {
627
-            this.presMap['statsns'] = 'http://jitsi.org/jitmeet/stats';
628
-            this.presMap['stats'] = stats;
629
-        },
630
-        findJidFromResource: function (resourceJid) {
631
-            if (resourceJid &&
632
-                resourceJid === Strophe.getResourceFromJid(this.myroomjid)) {
633
-                return this.myroomjid;
634
-            }
635
-            var peerJid = null;
636
-            Object.keys(this.members).some(function (jid) {
637
-                peerJid = jid;
638
-                return Strophe.getResourceFromJid(jid) === resourceJid;
639
-            });
640
-            return peerJid;
641
-        },
642
-        addBridgeIsDownToPresence: function () {
643
-            this.presMap['bridgeIsDown'] = true;
644
-        },
645
-        addEmailToPresence: function (email) {
646
-            this.presMap['email'] = email;
647
-        },
648
-        addUserIdToPresence: function (userId) {
649
-            this.presMap['userId'] = userId;
650
-        },
651
-        addStartMutedToPresence: function (audio, video) {
652
-            this.presMap["startMuted"] = {audio: audio, video: video};
653
-        },
654
-        isModerator: function () {
655
-            return this.role === 'moderator';
656
-        },
657
-        getMemberRole: function (peerJid) {
658
-            if (this.members[peerJid]) {
659
-                return this.members[peerJid].role;
660
-            }
661
-            return null;
662
-        },
663
-        onParticipantLeft: function (jid) {
664
-
665
-            eventEmitter.emit(XMPPEvents.MUC_MEMBER_LEFT, jid);
666
-
667
-            this.connection.jingle.terminateByJid(jid);
668
-
669
-            if (this.getPrezi(jid)) {
670
-                $(document).trigger('presentationremoved.muc',
671
-                    [jid, this.getPrezi(jid)]);
672
-            }
673
-
674
-            Moderator.onMucMemberLeft(jid);
675
-        },
676
-        parsePresence: function (from, member, pres) {
677
-            if($(pres).find(">bridgeIsDown").length > 0 && !this.bridgeIsDown) {
678
-                this.bridgeIsDown = true;
679
-                eventEmitter.emit(XMPPEvents.BRIDGE_DOWN);
680
-            }
681
-
682
-            if(member.isFocus)
683
-                return;
684
-
685
-            var displayName = !config.displayJids
686
-                ? member.displayName : Strophe.getResourceFromJid(from);
687
-
688
-            if (displayName && displayName.length > 0) {
689
-                eventEmitter.emit(XMPPEvents.DISPLAY_NAME_CHANGED, from, displayName);
690
-            }
691
-
692
-            var id = $(pres).find('>userID').text();
693
-            var email = $(pres).find('>email');
694
-            if (email.length > 0) {
695
-                id = email.text();
696
-            }
697
-
698
-            eventEmitter.emit(XMPPEvents.USER_ID_CHANGED, from, id);
699
-        }
700
-    });
701
-};
702
-

+ 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
- 61
modules/xmpp/strophe.moderate.js 查看文件

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

+ 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 10 sec
7
- */
8
-var PING_INTERVAL = 10000;
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
- 624
modules/xmpp/xmpp.js 查看文件

@@ -1,624 +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
-        connect(jid);
312
-    },
313
-    createConnection: function () {
314
-        var bosh = config.bosh || '/http-bind';
315
-        // adds the room name used to the bosh connection
316
-        bosh +=  '?room=' + APP.UI.getRoomNode();
317
-        if (config.token) {
318
-            bosh += "&token=" + config.token;
319
-        }
320
-        return new Strophe.Connection(bosh);
321
-    },
322
-    getStatusString: function (status) {
323
-        return Strophe.getStatusString(status);
324
-    },
325
-    promptLogin: function () {
326
-        eventEmitter.emit(XMPPEvents.PROMPT_FOR_LOGIN, connect);
327
-    },
328
-    joinRoom: function(roomName, useNicks, nick) {
329
-        var roomjid = roomName;
330
-
331
-        if (useNicks) {
332
-            if (nick) {
333
-                roomjid += '/' + nick;
334
-            } else {
335
-                roomjid += '/' + Strophe.getNodeFromJid(connection.jid);
336
-            }
337
-        } else {
338
-            var tmpJid = Strophe.getNodeFromJid(connection.jid);
339
-
340
-            if(!authenticatedUser)
341
-                tmpJid = tmpJid.substr(0, 8);
342
-
343
-            roomjid += '/' + tmpJid;
344
-        }
345
-        connection.emuc.doJoin(roomjid);
346
-    },
347
-    myJid: function () {
348
-        if(!connection)
349
-            return null;
350
-        return connection.emuc.myroomjid;
351
-    },
352
-    myResource: function () {
353
-        if(!connection || ! connection.emuc.myroomjid)
354
-            return null;
355
-        return Strophe.getResourceFromJid(connection.emuc.myroomjid);
356
-    },
357
-    getLastPresence: function (from) {
358
-        if(!connection)
359
-            return null;
360
-        return connection.emuc.lastPresenceMap[from];
361
-    },
362
-    disposeConference: function (onUnload) {
363
-        var handler = connection.jingle.activecall;
364
-        if (handler && handler.peerconnection) {
365
-            // FIXME: probably removing streams is not required and close() should
366
-            // be enough
367
-            if (APP.RTC.localAudio) {
368
-                handler.peerconnection.removeStream(
369
-                    APP.RTC.localAudio.getOriginalStream(), onUnload);
370
-            }
371
-            if (APP.RTC.localVideo) {
372
-                handler.peerconnection.removeStream(
373
-                    APP.RTC.localVideo.getOriginalStream(), onUnload);
374
-            }
375
-            handler.peerconnection.close();
376
-        }
377
-        eventEmitter.emit(XMPPEvents.DISPOSE_CONFERENCE, onUnload);
378
-        connection.jingle.activecall = null;
379
-        if (!onUnload) {
380
-            this.sessionTerminated = true;
381
-            connection.emuc.doLeave();
382
-        }
383
-    },
384
-    addListener: function(type, listener) {
385
-        eventEmitter.on(type, listener);
386
-    },
387
-    removeListener: function (type, listener) {
388
-        eventEmitter.removeListener(type, listener);
389
-    },
390
-    allocateConferenceFocus: function(roomName, callback) {
391
-        Moderator.allocateConferenceFocus(roomName, callback);
392
-    },
393
-    getLoginUrl: function (roomName, callback) {
394
-        Moderator.getLoginUrl(roomName, callback);
395
-    },
396
-    getPopupLoginUrl: function (roomName, callback) {
397
-        Moderator.getPopupLoginUrl(roomName, callback);
398
-    },
399
-    isModerator: function () {
400
-        return Moderator.isModerator();
401
-    },
402
-    isSipGatewayEnabled: function () {
403
-        return Moderator.isSipGatewayEnabled();
404
-    },
405
-    isExternalAuthEnabled: function () {
406
-        return Moderator.isExternalAuthEnabled();
407
-    },
408
-    isConferenceInProgress: function () {
409
-        return connection && connection.jingle.activecall &&
410
-            connection.jingle.activecall.peerconnection;
411
-    },
412
-    switchStreams: function (stream, oldStream, callback, isAudio) {
413
-        if (this.isConferenceInProgress()) {
414
-            // FIXME: will block switchInProgress on true value in case of exception
415
-            connection.jingle.activecall.switchStreams(stream, oldStream, callback, isAudio);
416
-        } else {
417
-            // We are done immediately
418
-            console.warn("No conference handler or conference not started yet");
419
-            callback();
420
-        }
421
-    },
422
-    sendVideoInfoPresence: function (mute) {
423
-        if(!connection)
424
-            return;
425
-        connection.emuc.addVideoInfoToPresence(mute);
426
-        connection.emuc.sendPresence();
427
-    },
428
-    setVideoMute: function (mute, callback, options) {
429
-        if(!connection)
430
-            return;
431
-        var self = this;
432
-        var localCallback = function (mute) {
433
-            self.sendVideoInfoPresence(mute);
434
-            return callback(mute);
435
-        };
436
-
437
-        if(connection.jingle.activecall)
438
-        {
439
-            connection.jingle.activecall.setVideoMute(
440
-                mute, localCallback, options);
441
-        }
442
-        else {
443
-            localCallback(mute);
444
-        }
445
-
446
-    },
447
-    setAudioMute: function (mute, callback) {
448
-        if (!(connection && APP.RTC.localAudio)) {
449
-            return false;
450
-        }
451
-
452
-        if (this.forceMuted && !mute) {
453
-            console.info("Asking focus for unmute");
454
-            connection.moderate.setMute(connection.emuc.myroomjid, mute);
455
-            // FIXME: wait for result before resetting muted status
456
-            this.forceMuted = false;
457
-        }
458
-
459
-        if (mute == APP.RTC.localAudio.isMuted()) {
460
-            // Nothing to do
461
-            return true;
462
-        }
463
-
464
-        APP.RTC.localAudio.setMute(mute);
465
-        this.sendAudioInfoPresence(mute, callback);
466
-        return true;
467
-    },
468
-    sendAudioInfoPresence: function(mute, callback) {
469
-        if(connection) {
470
-            connection.emuc.addAudioInfoToPresence(mute);
471
-            connection.emuc.sendPresence();
472
-        }
473
-        callback();
474
-        return true;
475
-    },
476
-    toggleRecording: function (tokenEmptyCallback,
477
-                               recordingStateChangeCallback) {
478
-        Recording.toggleRecording(tokenEmptyCallback,
479
-            recordingStateChangeCallback, connection);
480
-    },
481
-    addToPresence: function (name, value, dontSend) {
482
-        switch (name) {
483
-            case "displayName":
484
-                connection.emuc.addDisplayNameToPresence(value);
485
-                break;
486
-            case "prezi":
487
-                connection.emuc.addPreziToPresence(value, 0);
488
-                break;
489
-            case "preziSlide":
490
-                connection.emuc.addCurrentSlideToPresence(value);
491
-                break;
492
-            case "connectionQuality":
493
-                connection.emuc.addConnectionInfoToPresence(value);
494
-                break;
495
-            case "email":
496
-                connection.emuc.addEmailToPresence(value);
497
-                break;
498
-            case "devices":
499
-                connection.emuc.addDevicesToPresence(value);
500
-                break;
501
-            case "videoType":
502
-                connection.emuc.addVideoTypeToPresence(value);
503
-                break;
504
-            case "startMuted":
505
-                if(!Moderator.isModerator())
506
-                    return;
507
-                connection.emuc.addStartMutedToPresence(value[0],
508
-                    value[1]);
509
-                break;
510
-            default :
511
-                console.log("Unknown tag for presence: " + name);
512
-                return;
513
-        }
514
-        if (!dontSend)
515
-            connection.emuc.sendPresence();
516
-    },
517
-    /**
518
-     * Sends 'data' as a log message to the focus. Returns true iff a message
519
-     * was sent.
520
-     * @param data
521
-     * @returns {boolean} true iff a message was sent.
522
-     */
523
-    sendLogs: function (data) {
524
-        if(!connection.emuc.focusMucJid)
525
-            return false;
526
-
527
-        var deflate = true;
528
-
529
-        var content = JSON.stringify(data);
530
-        if (deflate) {
531
-            content = String.fromCharCode.apply(null, Pako.deflateRaw(content));
532
-        }
533
-        content = Base64.encode(content);
534
-        // XEP-0337-ish
535
-        var message = $msg({to: connection.emuc.focusMucJid, type: 'normal'});
536
-        message.c('log', { xmlns: 'urn:xmpp:eventlog',
537
-            id: 'PeerConnectionStats'});
538
-        message.c('message').t(content).up();
539
-        if (deflate) {
540
-            message.c('tag', {name: "deflated", value: "true"}).up();
541
-        }
542
-        message.up();
543
-
544
-        connection.send(message);
545
-        return true;
546
-    },
547
-    // Gets the logs from strophe.jingle.
548
-    getJingleLog: function () {
549
-        return connection.jingle ? connection.jingle.getLog() : {};
550
-    },
551
-    // Gets the logs from strophe.
552
-    getXmppLog: function () {
553
-        return connection.logger ? connection.logger.log : null;
554
-    },
555
-    getPrezi: function () {
556
-        return connection.emuc.getPrezi(this.myJid());
557
-    },
558
-    removePreziFromPresence: function () {
559
-        connection.emuc.removePreziFromPresence();
560
-        connection.emuc.sendPresence();
561
-    },
562
-    sendChatMessage: function (message, nickname) {
563
-        connection.emuc.sendMessage(message, nickname);
564
-    },
565
-    setSubject: function (topic) {
566
-        connection.emuc.setSubject(topic);
567
-    },
568
-    lockRoom: function (key, onSuccess, onError, onNotSupported) {
569
-        connection.emuc.lockRoom(key, onSuccess, onError, onNotSupported);
570
-    },
571
-    dial: function (to, from, roomName,roomPass) {
572
-        connection.rayo.dial(to, from, roomName,roomPass);
573
-    },
574
-    setMute: function (jid, mute) {
575
-        connection.moderate.setMute(jid, mute);
576
-    },
577
-    eject: function (jid) {
578
-        connection.moderate.eject(jid);
579
-    },
580
-    logout: function (callback) {
581
-        Moderator.logout(callback);
582
-    },
583
-    findJidFromResource: function (resource) {
584
-        return connection.emuc.findJidFromResource(resource);
585
-    },
586
-    getMembers: function () {
587
-        return connection.emuc.members;
588
-    },
589
-    getJidFromSSRC: function (ssrc) {
590
-        if (!this.isConferenceInProgress())
591
-            return null;
592
-        return connection.jingle.activecall.getSsrcOwner(ssrc);
593
-    },
594
-    /**
595
-     * Gets the SSRC of local media stream.
596
-     * @param mediaType the media type that tells whether we want to get
597
-     *        the SSRC of local audio or video stream.
598
-     * @returns {*} the SSRC number for local media stream or <tt>null</tt> if
599
-     *              not available.
600
-     */
601
-    getLocalSSRC: function (mediaType) {
602
-        if (!this.isConferenceInProgress()) {
603
-            return null;
604
-        }
605
-        return connection.jingle.activecall.getLocalSSRC(mediaType);
606
-    },
607
-    // Returns true iff we have joined the MUC.
608
-    isMUCJoined: function () {
609
-        return connection === null ? false : connection.emuc.joined;
610
-    },
611
-    getSessions: function () {
612
-        return connection.jingle.sessions;
613
-    },
614
-    removeStream: function (stream) {
615
-        if (!this.isConferenceInProgress())
616
-            return;
617
-        connection.jingle.activecall.peerconnection.removeStream(stream);
618
-    },
619
-    filter_special_chars: function (text) {
620
-        return SDPUtil.filter_special_chars(text);
621
-    }
622
-};
623
-
624
-module.exports = XMPP;

+ 11
- 2
package.json 查看文件

@@ -42,7 +42,10 @@
42 42
     "jshint": "2.8.0",
43 43
     "precommit-hook": "3.0.0",
44 44
     "uglify-js": "2.4.24",
45
-    "clean-css": "*"
45
+    "clean-css": "*",
46
+    "babelify": "*",
47
+    "babel-preset-es2015": "*",
48
+    "babel-polyfill": "*"
46 49
   },
47 50
   "license": "Apache-2.0",
48 51
   "scripts": {
@@ -54,9 +57,15 @@
54 57
   ],
55 58
   "browserify": {
56 59
     "transform": [
57
-      "browserify-shim"
60
+      "browserify-shim",
61
+      ["babelify", {
62
+        "ignore": "node_modules"
63
+      }]
58 64
     ]
59 65
   },
66
+  "babel": {
67
+    "presets": ["es2015"]
68
+  },
60 69
   "browser": {
61 70
     "jquery": "./node_modules/jquery/dist/jquery.js",
62 71
     "jquery-ui": "./node_modules/jquery-ui/jquery-ui.js",

+ 12
- 0
prosody-plugins/mod_bosh.lua.patch 查看文件

@@ -0,0 +1,12 @@
1
+--- /usr/lib/prosody/modules/mod_bosh.lua	2015-12-16 14:28:34.000000000 -0600
2
++++ /usr/lib/prosody/modules/mod_bosh.lua	2015-12-22 10:45:59.818197967 -0600
3
+@@ -294,6 +294,9 @@
4
+ 
5
+ 		session.log("debug", "BOSH session created for request from %s", session.ip);
6
+ 		log("info", "New BOSH session, assigned it sid '%s'", sid);
7
++		
8
++		hosts[session.host].events.fire_event(
9
++			"bosh-session", { session = session, request = request });
10
+ 
11
+ 		-- Send creation response
12
+ 		local creating_session = true;

+ 0
- 6
service/RTC/MediaStreamTypes.js 查看文件

@@ -1,6 +0,0 @@
1
-var MediaStreamType = {
2
-    VIDEO_TYPE: "video",
3
-
4
-    AUDIO_TYPE: "audio"
5
-};
6
-module.exports = MediaStreamType;

+ 0
- 53
service/RTC/Resolutions.js 查看文件

@@ -1,53 +0,0 @@
1
-var Resolutions = {
2
-    "1080": {
3
-        width: 1920,
4
-        height: 1080,
5
-        order: 7
6
-    },
7
-    "fullhd": {
8
-        width: 1920,
9
-        height: 1080,
10
-        order: 7
11
-    },
12
-    "720": {
13
-        width: 1280,
14
-        height: 720,
15
-        order: 6
16
-    },
17
-    "hd": {
18
-        width: 1280,
19
-        height: 720,
20
-        order: 6
21
-    },
22
-    "960": {
23
-        width: 960,
24
-        height: 720,
25
-        order: 5
26
-    },
27
-    "640": {
28
-        width: 640,
29
-        height: 480,
30
-        order: 4
31
-    },
32
-    "vga": {
33
-        width: 640,
34
-        height: 480,
35
-        order: 4
36
-    },
37
-    "360": {
38
-        width: 640,
39
-        height: 360,
40
-        order: 3
41
-    },
42
-    "320": {
43
-        width: 320,
44
-        height: 240,
45
-        order: 2
46
-    },
47
-    "180": {
48
-        width: 320,
49
-        height: 180,
50
-        order: 1
51
-    }
52
-};
53
-module.exports = Resolutions;

+ 39
- 6
service/UI/UIEvents.js 查看文件

@@ -1,12 +1,45 @@
1
-var UIEvents = {
1
+export default {
2 2
     NICKNAME_CHANGED: "UI.nickname_changed",
3 3
     SELECTED_ENDPOINT: "UI.selected_endpoint",
4 4
     PINNED_ENDPOINT: "UI.pinned_endpoint",
5
-    LARGEVIDEO_INIT: "UI.largevideo_init",
6 5
     /**
7
-     * Notifies interested parties when the film strip (remote video's panel)
8
-     * is hidden (toggled) or shown (un-toggled).
6
+     * Notifies that local user created text message.
9 7
      */
10
-    FILM_STRIP_TOGGLED: "UI.filmstrip_toggled"
8
+    MESSAGE_CREATED: "UI.message_created",
9
+    /**
10
+     * Notifies that local user changed language.
11
+     */
12
+    LANG_CHANGED: "UI.lang_changed",
13
+    /**
14
+     * Notifies that local user changed email.
15
+     */
16
+    EMAIL_CHANGED: "UI.email_changed",
17
+    /**
18
+     * Notifies that "start muted" settings changed.
19
+     */
20
+    START_MUTED_CHANGED: "UI.start_muted_changed",
21
+    AUDIO_MUTED: "UI.audio_muted",
22
+    VIDEO_MUTED: "UI.video_muted",
23
+    PREZI_CLICKED: "UI.prezi_clicked",
24
+    SHARE_PREZI: "UI.share_prezi",
25
+    PREZI_SLIDE_CHANGED: "UI.prezi_slide_changed",
26
+    STOP_SHARING_PREZI: "UI.stop_sharing_prezi",
27
+    ETHERPAD_CLICKED: "UI.etherpad_clicked",
28
+    ROOM_LOCK_CLICKED: "UI.room_lock_clicked",
29
+    USER_INVITED: "UI.user_invited",
30
+    USER_KICKED: "UI.user_kicked",
31
+    REMOTE_AUDIO_MUTED: "UI.remote_audio_muted",
32
+    FULLSCREEN_TOGGLE: "UI.fullscreen_toggle",
33
+    AUTH_CLICKED: "UI.auth_clicked",
34
+    TOGGLE_CHAT: "UI.toggle_chat",
35
+    TOGGLE_SETTINGS: "UI.toggle_settings",
36
+    TOGGLE_CONTACT_LIST: "UI.toggle_contact_list",
37
+    TOGGLE_FILM_STRIP: "UI.toggle_film_strip",
38
+    TOGGLE_SCREENSHARING: "UI.toggle_screensharing",
39
+    CONTACT_CLICKED: "UI.contact_clicked",
40
+    HANGUP: "UI.hangup",
41
+    LOGOUT: "UI.logout",
42
+    RECORDING_TOGGLE: "UI.recording_toggle",
43
+    SIP_DIAL: "UI.sip_dial",
44
+    SUBEJCT_CHANGED: "UI.subject_changed"
11 45
 };
12
-module.exports = UIEvents;

+ 1
- 5
service/desktopsharing/DesktopSharingEventTypes.js 查看文件

@@ -1,6 +1,4 @@
1
-var DesktopSharingEventTypes = {
2
-    INIT: "ds.init",
3
-
1
+export default {
4 2
     SWITCHING_DONE: "ds.switching_done",
5 3
 
6 4
     NEW_STREAM_CREATED: "ds.new_stream_created",
@@ -11,5 +9,3 @@ var DesktopSharingEventTypes = {
11 9
      */
12 10
     FIREFOX_EXTENSION_NEEDED: "ds.firefox_extension_needed"
13 11
 };
14
-
15
-module.exports = DesktopSharingEventTypes;

+ 0
- 5
service/members/Events.js 查看文件

@@ -1,5 +0,0 @@
1
-var Events = {
2
-    DTMF_SUPPORT_CHANGED: "members.dtmf_support_changed"
3
-};
4
-
5
-module.exports = Events;

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

@@ -86,8 +86,6 @@ var XMPPEvents = {
86 86
     JINGLE_FATAL_ERROR: 'xmpp.jingle_fatal_error',
87 87
     PROMPT_FOR_LOGIN: 'xmpp.prompt_for_login',
88 88
     FOCUS_DISCONNECTED: 'xmpp.focus_disconnected',
89
-    ROOM_JOIN_ERROR: 'xmpp.room_join_error',
90
-    ROOM_CONNECT_ERROR: 'xmpp.room_connect_error',
91 89
     // xmpp is connected and obtained user media
92 90
     READY_TO_JOIN: 'xmpp.ready_to_join'
93 91
 };

正在加载...
取消
保存