Преглед изворни кода

Merge remote-tracking branch 'origin/master' into issue/toolbar-refactor

j8
Issac Gerges пре 10 година
родитељ
комит
de30ce0f5c

+ 1
- 0
.gitignore Прегледај датотеку

@@ -3,3 +3,4 @@ node_modules
3 3
 .idea/
4 4
 *.iml
5 5
 .*.tmp
6
+deploy-local.sh

+ 5
- 2
Makefile Прегледај датотеку

@@ -18,7 +18,10 @@ app:
18 18
 	$(NPM) update && $(BROWSERIFY) $(FLAGS) app.js -s APP -o $(OUTPUT_DIR)/app.bundle.js
19 19
 
20 20
 clean:
21
-	@rm -f $(OUTPUT_DIR)/*.bundle.js
21
+	rm -f $(OUTPUT_DIR)/*.bundle.js
22 22
 
23 23
 deploy:
24
-	@mkdir -p $(DEPLOY_DIR) && cp $(OUTPUT_DIR)/*.bundle.js $(DEPLOY_DIR) && ./bump-js-versions.sh
24
+	mkdir -p $(DEPLOY_DIR) && \
25
+	cp $(OUTPUT_DIR)/*.bundle.js $(DEPLOY_DIR) && \
26
+	./bump-js-versions.sh && \
27
+	([ ! -x deploy-local.sh ] || ./deploy-local.sh)

+ 5
- 0
css/videolayout_default.css Прегледај датотеку

@@ -446,6 +446,11 @@
446 446
     background-position: center;
447 447
 }
448 448
 
449
+.videoMessageFilter {
450
+    -webkit-filter: grayscale(.5) opacity(0.8);
451
+    filter: grayscale(.5) opacity(0.8);
452
+}
453
+
449 454
 .videoProblemFilter {
450 455
     -webkit-filter: blur(10px) grayscale(.5) opacity(0.8);
451 456
     filter: blur(10px) grayscale(.5) opacity(0.8);

+ 5
- 0
doc/api.md Прегледај датотеку

@@ -29,6 +29,11 @@ constructor.
29 29
 ``` 
30 30
 If you don't specify room the user will enter in new conference with random room name.
31 31
 
32
+You can enable the "film strip only" mode(only the small videos are visible) by setting 6th parameter to ```true```:
33
+```javascript
34
+    var api = new JitsiMeetExternalAPI(domain, room, width, height, htmlElement, true);
35
+``` 
36
+
32 37
 Controlling embedded Jitsi Meet Conference
33 38
 =========
34 39
 

+ 7
- 1
external_api.js Прегледај датотеку

@@ -23,9 +23,12 @@ var JitsiMeetExternalAPI = (function()
23 23
      * @param width width of the iframe
24 24
      * @param height height of the iframe
25 25
      * @param parent_node the node that will contain the iframe
26
+     * @param filmStripOnly if the value is true only the small videos will be
27
+     * visible.
26 28
      * @constructor
27 29
      */
28
-    function JitsiMeetExternalAPI(domain, room_name, width, height, parentNode) {
30
+    function JitsiMeetExternalAPI(domain, room_name, width, height, parentNode,
31
+        filmStripOnly) {
29 32
         if(!width || width < MIN_WIDTH)
30 33
             width = MIN_WIDTH;
31 34
         if(!height || height < MIN_HEIGHT)
@@ -49,6 +52,9 @@ var JitsiMeetExternalAPI = (function()
49 52
         if(room_name)
50 53
             this.url += room_name;
51 54
         this.url += "#external=true";
55
+        if(filmStripOnly)
56
+            this.url += "&interfaceConfig.filmStripOnly=true";
57
+
52 58
         JitsiMeetExternalAPI.id++;
53 59
 
54 60
         this.frame = document.createElement("iframe");

+ 1
- 1
index.html Прегледај датотеку

@@ -22,7 +22,7 @@
22 22
     <script src="libs/popover.js?v=1"></script><!-- bootstrap tooltip lib -->
23 23
     <script src="libs/toastr.js?v=1"></script><!-- notifications lib -->
24 24
     <script src="interface_config.js?v=5"></script>
25
-    <script src="libs/app.bundle.js?v=120"></script>
25
+    <script src="libs/app.bundle.js?v=121"></script>
26 26
     <script src="analytics.js?v=1"></script><!-- google analytics plugin -->
27 27
     <link rel="stylesheet" href="css/font.css?v=7"/>
28 28
     <link rel="stylesheet" href="css/toastr.css?v=1">

+ 5
- 1
interface_config.js Прегледај датотеку

@@ -15,5 +15,9 @@ var interfaceConfig = {
15 15
     GENERATE_ROOMNAMES_ON_WELCOME_PAGE: true,
16 16
     APP_NAME: "Jitsi Meet",
17 17
     INVITATION_POWERED_BY: true,
18
-    ACTIVE_SPEAKER_AVATAR_SIZE: 100
18
+    ACTIVE_SPEAKER_AVATAR_SIZE: 100,
19
+    /**
20
+     * Whether to only show the filmstrip (and hide the toolbar).
21
+     */
22
+    filmStripOnly: false
19 23
 };

+ 6
- 0
lang/main.json Прегледај датотеку

@@ -240,5 +240,11 @@
240 240
         "FETCH_SESSION_ID": "Obtaining session-id...",
241 241
         "GOT_SESSION_ID": "Obtaining session-id... Done",
242 242
         "GET_SESSION_ID_ERROR": "Get session-id error: "
243
+    },
244
+    "recording":
245
+    {
246
+        "toaster": "Currently recording!",
247
+        "pending": "Your recording will start as soon as another participant joins",
248
+        "on": "Recording has been started"
243 249
     }
244 250
 }

+ 997
- 604
libs/app.bundle.js
Разлика између датотеке није приказан због своје велике величине
Прегледај датотеку


+ 26
- 3
modules/RTC/LocalStream.js Прегледај датотеку

@@ -1,6 +1,24 @@
1 1
 /* global APP */
2 2
 var StreamEventTypes = require("../../service/RTC/StreamEventTypes.js");
3 3
 var RTCEvents = require("../../service/RTC/RTCEvents");
4
+var RTCBrowserType = require("./RTCBrowserType");
5
+
6
+/**
7
+ * This implements 'onended' callback normally fired by WebRTC after the stream
8
+ * is stopped. There is no such behaviour yet in FF, so we have to add it.
9
+ * @param stream original WebRTC stream object to which 'onended' handling
10
+ *               will be added.
11
+ */
12
+function implementOnEndedHandling(stream) {
13
+    var originalStop = stream.stop;
14
+    stream.stop = function () {
15
+        originalStop.apply(stream);
16
+        if (!stream.ended) {
17
+            stream.ended = true;
18
+            stream.onended();
19
+        }
20
+    };
21
+}
4 22
 
5 23
 function LocalStream(stream, type, eventEmitter, videoType, isGUMStream) {
6 24
     this.stream = stream;
@@ -21,9 +39,12 @@ function LocalStream(stream, type, eventEmitter, videoType, isGUMStream) {
21 39
         };
22 40
     }
23 41
 
24
-    this.stream.onended = function() {
42
+    this.stream.onended = function () {
25 43
         self.streamEnded();
26 44
     };
45
+    if (RTCBrowserType.isFirefox()) {
46
+        implementOnEndedHandling(this.stream);
47
+    }
27 48
 }
28 49
 
29 50
 LocalStream.prototype.streamEnded = function () {
@@ -45,9 +66,11 @@ LocalStream.prototype.setMute = function (mute)
45 66
     var eventType = isAudio ? RTCEvents.AUDIO_MUTE : RTCEvents.VIDEO_MUTE;
46 67
 
47 68
     if ((window.location.protocol != "https:" && this.isGUMStream) ||
48
-        (isAudio && this.isGUMStream) || this.videoType === "screen") {
49
-        var tracks = this.getTracks();
69
+        (isAudio && this.isGUMStream) || this.videoType === "screen" ||
70
+        // FIXME FF does not support 'removeStream' method used to mute
71
+        RTCBrowserType.isFirefox()) {
50 72
 
73
+        var tracks = this.getTracks();
51 74
         for (var idx = 0; idx < tracks.length; idx++) {
52 75
             tracks[idx].enabled = !mute;
53 76
         }

+ 27
- 37
modules/RTC/RTCUtils.js Прегледај датотеку

@@ -23,21 +23,17 @@ function getPreviousResolution(resolution) {
23 23
 }
24 24
 
25 25
 function setResolutionConstraints(constraints, resolution, isAndroid) {
26
-    if (resolution && !constraints.video || isAndroid) {
27
-        // same behaviour as true
28
-        constraints.video = { mandatory: {}, optional: [] };
29
-    }
30 26
 
31
-    if(Resolutions[resolution]) {
27
+    if (Resolutions[resolution]) {
32 28
         constraints.video.mandatory.minWidth = Resolutions[resolution].width;
33 29
         constraints.video.mandatory.minHeight = Resolutions[resolution].height;
34 30
     }
35
-    else {
36
-        if (isAndroid) {
37
-            constraints.video.mandatory.minWidth = 320;
38
-            constraints.video.mandatory.minHeight = 240;
39
-            constraints.video.mandatory.maxFrameRate = 15;
40
-        }
31
+    else if (isAndroid) {
32
+        // FIXME can't remember if the purpose of this was to always request
33
+        //       low resolution on Android ? if yes it should be moved up front
34
+        constraints.video.mandatory.minWidth = 320;
35
+        constraints.video.mandatory.minHeight = 240;
36
+        constraints.video.mandatory.maxFrameRate = 15;
41 37
     }
42 38
 
43 39
     if (constraints.video.mandatory.minWidth)
@@ -55,10 +51,28 @@ function getConstraints(um, resolution, bandwidth, fps, desktopStream, isAndroid
55 51
     if (um.indexOf('video') >= 0) {
56 52
         // same behaviour as true
57 53
         constraints.video = { mandatory: {}, optional: [] };
54
+
55
+        constraints.video.optional.push({ googLeakyBucket: true });
56
+
57
+        setResolutionConstraints(constraints, resolution, isAndroid);
58 58
     }
59 59
     if (um.indexOf('audio') >= 0) {
60
-        // same behaviour as true
61
-        constraints.audio = { mandatory: {}, optional: []};
60
+        if (!RTCBrowserType.isFirefox()) {
61
+            // same behaviour as true
62
+            constraints.audio = { mandatory: {}, optional: []};
63
+            // if it is good enough for hangouts...
64
+            constraints.audio.optional.push(
65
+                {googEchoCancellation: true},
66
+                {googAutoGainControl: true},
67
+                {googNoiseSupression: true},
68
+                {googHighpassFilter: true},
69
+                {googNoisesuppression2: true},
70
+                {googEchoCancellation2: true},
71
+                {googAutoGainControl2: true}
72
+            );
73
+        } else {
74
+            constraints.audio = true;
75
+        }
62 76
     }
63 77
     if (um.indexOf('screen') >= 0) {
64 78
         if (RTCBrowserType.isChrome()) {
@@ -100,30 +114,6 @@ function getConstraints(um, resolution, bandwidth, fps, desktopStream, isAndroid
100 114
         };
101 115
     }
102 116
 
103
-    if (constraints.audio) {
104
-        // if it is good enough for hangouts...
105
-        constraints.audio.optional.push(
106
-            {googEchoCancellation: true},
107
-            {googAutoGainControl: true},
108
-            {googNoiseSupression: true},
109
-            {googHighpassFilter: true},
110
-            {googNoisesuppression2: true},
111
-            {googEchoCancellation2: true},
112
-            {googAutoGainControl2: true}
113
-        );
114
-    }
115
-    if (constraints.video) {
116
-        if (um.indexOf('video') >= 0) {
117
-            constraints.video.optional.push(
118
-                {googLeakyBucket: true}
119
-            );
120
-        }
121
-    }
122
-
123
-    if (um.indexOf('video') >= 0) {
124
-        setResolutionConstraints(constraints, resolution, isAndroid);
125
-    }
126
-
127 117
     if (bandwidth) {
128 118
         if (!constraints.video) {
129 119
             //same behaviour as true

+ 84
- 74
modules/UI/UI.js Прегледај датотеку

@@ -21,6 +21,7 @@ var messageHandler = UI.messageHandler;
21 21
 var Authentication  = require("./authentication/Authentication");
22 22
 var UIUtil = require("./util/UIUtil");
23 23
 var NicknameHandler = require("./util/NicknameHandler");
24
+var JitsiPopover = require("./util/JitsiPopover");
24 25
 var CQEvents = require("../../service/connectionquality/CQEvents");
25 26
 var DesktopSharingEventTypes
26 27
     = require("../../service/desktopsharing/DesktopSharingEventTypes");
@@ -169,13 +170,13 @@ function registerListeners() {
169 170
             VideoLayout.setDeviceAvailabilityIcons(null, devices);
170 171
         });
171 172
     APP.RTC.addListener(RTCEvents.VIDEO_MUTE, UI.setVideoMuteButtonsState);
172
-    APP.RTC.addListener(RTCEvents.DATA_CHANNEL_OPEN, function() {
173
+    APP.RTC.addListener(RTCEvents.DATA_CHANNEL_OPEN, function () {
173 174
         // when the data channel becomes available, tell the bridge about video
174 175
         // selections so that it can do adaptive simulcast,
175 176
         // we want the notification to trigger even if userJid is undefined,
176 177
         // or null.
177
-        var userJid = APP.UI.getLargeVideoJid();
178
-        eventEmitter.emit(UIEvents.SELECTED_ENDPOINT, userJid);
178
+        var userResource = APP.UI.getLargeVideoResource();
179
+        eventEmitter.emit(UIEvents.SELECTED_ENDPOINT, userResource);
179 180
     });
180 181
     APP.statistics.addAudioLevelListener(function(jid, audioLevel) {
181 182
         var resourceJid;
@@ -189,7 +190,7 @@ function registerListeners() {
189 190
         }
190 191
 
191 192
         AudioLevels.updateAudioLevel(resourceJid, audioLevel,
192
-            UI.getLargeVideoJid());
193
+            UI.getLargeVideoResource());
193 194
     });
194 195
     APP.desktopsharing.addListener(function () {
195 196
         ToolbarToggler.showDesktopSharingButton();
@@ -254,10 +255,8 @@ function registerListeners() {
254 255
     APP.xmpp.addListener(XMPPEvents.MUC_ROLE_CHANGED, onMucRoleChanged);
255 256
     APP.xmpp.addListener(XMPPEvents.PRESENCE_STATUS, onMucPresenceStatus);
256 257
     APP.xmpp.addListener(XMPPEvents.SUBJECT_CHANGED, chatSetSubject);
257
-    APP.xmpp.addListener(XMPPEvents.MESSAGE_RECEIVED, updateChatConversation);
258 258
     APP.xmpp.addListener(XMPPEvents.MUC_MEMBER_LEFT, onMucMemberLeft);
259 259
     APP.xmpp.addListener(XMPPEvents.PASSWORD_REQUIRED, onPasswordRequired);
260
-    APP.xmpp.addListener(XMPPEvents.CHAT_ERROR_RECEIVED, chatAddError);
261 260
     APP.xmpp.addListener(XMPPEvents.ETHERPAD, initEtherpad);
262 261
     APP.xmpp.addListener(XMPPEvents.AUTHENTICATION_REQUIRED,
263 262
         onAuthenticationRequired);
@@ -269,11 +268,11 @@ function registerListeners() {
269 268
 
270 269
     APP.xmpp.addListener(XMPPEvents.AUDIO_MUTED, VideoLayout.onAudioMute);
271 270
     APP.xmpp.addListener(XMPPEvents.VIDEO_MUTED, VideoLayout.onVideoMute);
272
-    APP.xmpp.addListener(XMPPEvents.AUDIO_MUTED_BY_FOCUS, function(doMuteAudio) {
271
+    APP.xmpp.addListener(XMPPEvents.AUDIO_MUTED_BY_FOCUS, function (doMuteAudio) {
273 272
         UI.setAudioMuted(doMuteAudio);
274 273
     });
275 274
     APP.members.addListener(MemberEvents.DTMF_SUPPORT_CHANGED,
276
-                            onDtmfSupportChanged);
275
+        onDtmfSupportChanged);
277 276
     APP.xmpp.addListener(XMPPEvents.START_MUTED_SETTING_CHANGED, function (audio, video) {
278 277
         SettingsMenu.setStartMuted(audio, video);
279 278
     });
@@ -286,43 +285,43 @@ function registerListeners() {
286 285
             "dialog.internalError");
287 286
     });
288 287
 
289
-    APP.xmpp.addListener(XMPPEvents.SET_LOCAL_DESCRIPTION_ERROR, function() {
288
+    APP.xmpp.addListener(XMPPEvents.SET_LOCAL_DESCRIPTION_ERROR, function () {
290 289
         messageHandler.showError("dialog.error",
291
-                                        "dialog.SLDFailure");
290
+            "dialog.SLDFailure");
292 291
     });
293
-    APP.xmpp.addListener(XMPPEvents.SET_REMOTE_DESCRIPTION_ERROR, function() {
292
+    APP.xmpp.addListener(XMPPEvents.SET_REMOTE_DESCRIPTION_ERROR, function () {
294 293
         messageHandler.showError("dialog.error",
295 294
             "dialog.SRDFailure");
296 295
     });
297
-    APP.xmpp.addListener(XMPPEvents.CREATE_ANSWER_ERROR, function() {
296
+    APP.xmpp.addListener(XMPPEvents.CREATE_ANSWER_ERROR, function () {
298 297
         messageHandler.showError();
299 298
     });
300
-    APP.xmpp.addListener(XMPPEvents.PROMPT_FOR_LOGIN, function() {
299
+    APP.xmpp.addListener(XMPPEvents.PROMPT_FOR_LOGIN, function () {
301 300
         // FIXME: re-use LoginDialog which supports retries
302 301
         UI.showLoginPopup(connect);
303 302
     });
304
-    
305
-    APP.xmpp.addListener(XMPPEvents.FOCUS_DISCONNECTED, function(focusComponent, retrySec) {
303
+
304
+    APP.xmpp.addListener(XMPPEvents.FOCUS_DISCONNECTED, function (focusComponent, retrySec) {
306 305
         UI.messageHandler.notify(
307 306
             null, "notify.focus",
308 307
             'disconnected', "notify.focusFail",
309 308
             {component: focusComponent, ms: retrySec});
310 309
     });
311
-    
312
-    APP.xmpp.addListener(XMPPEvents.ROOM_JOIN_ERROR, function(pres) {
310
+
311
+    APP.xmpp.addListener(XMPPEvents.ROOM_JOIN_ERROR, function (pres) {
313 312
         UI.messageHandler.openReportDialog(null,
314 313
             "dialog.joinError", pres);
315 314
     });
316
-    APP.xmpp.addListener(XMPPEvents.ROOM_CONNECT_ERROR, function(pres) {
315
+    APP.xmpp.addListener(XMPPEvents.ROOM_CONNECT_ERROR, function (pres) {
317 316
         UI.messageHandler.openReportDialog(null,
318 317
             "dialog.connectError", pres);
319 318
     });
320 319
 
321
-    APP.xmpp.addListener(XMPPEvents.READY_TO_JOIN, function() {
320
+    APP.xmpp.addListener(XMPPEvents.READY_TO_JOIN, function () {
322 321
         var roomName = UI.generateRoomName();
323 322
         APP.xmpp.allocateConferenceFocus(roomName, UI.checkForNicknameAndJoin);
324 323
     });
325
-    
324
+
326 325
     //NicknameHandler emits this event
327 326
     UI.addListener(UIEvents.NICKNAME_CHANGED, function (nickname) {
328 327
         APP.xmpp.addToPresence("displayName", nickname);
@@ -332,10 +331,14 @@ function registerListeners() {
332 331
         AudioLevels.init();
333 332
     });
334 333
 
335
-    // Listens for video interruption events.
336
-    APP.xmpp.addListener(XMPPEvents.CONNECTION_INTERRUPTED, VideoLayout.onVideoInterrupted);
337
-    // Listens for video restores events.
338
-    APP.xmpp.addListener(XMPPEvents.CONNECTION_RESTORED, VideoLayout.onVideoRestored);
334
+    if (!interfaceConfig.filmStripOnly) {
335
+        APP.xmpp.addListener(XMPPEvents.MESSAGE_RECEIVED, updateChatConversation);
336
+        APP.xmpp.addListener(XMPPEvents.CHAT_ERROR_RECEIVED, chatAddError);
337
+        // Listens for video interruption events.
338
+        APP.xmpp.addListener(XMPPEvents.CONNECTION_INTERRUPTED, VideoLayout.onVideoInterrupted);
339
+        // Listens for video restores events.
340
+        APP.xmpp.addListener(XMPPEvents.CONNECTION_RESTORED, VideoLayout.onVideoRestored);
341
+    }
339 342
 }
340 343
 
341 344
 
@@ -385,9 +388,6 @@ UI.start = function (init) {
385 388
 
386 389
     $("#welcome_page").hide();
387 390
 
388
-    $("#videospace").mousemove(function () {
389
-        return ToolbarToggler.showToolbar();
390
-    });
391 391
     // Set the defaults for prompt dialogs.
392 392
     $.prompt.setDefaults({persistent: false});
393 393
 
@@ -399,34 +399,39 @@ UI.start = function (init) {
399 399
 
400 400
     bindEvents();
401 401
     setupPrezi();
402
-    setupToolbars();
403
-    setupChat();
404
-
402
+    if (!interfaceConfig.filmStripOnly) {
403
+        $("#videospace").mousemove(function () {
404
+            return ToolbarToggler.showToolbar();
405
+        });
406
+        setupToolbars();
407
+        setupChat();
408
+        // Display notice message at the top of the toolbar
409
+        if (config.noticeMessage) {
410
+            $('#noticeText').text(config.noticeMessage);
411
+            $('#notice').css({display: 'block'});
412
+        }
413
+        $("#downloadlog").click(function (event) {
414
+            dump(event.target);
415
+        });
416
+    }
417
+    else
418
+    {
419
+        $("#header").css("display", "none");
420
+        $("#bottomToolbar").css("display", "none");
421
+        $("#downloadlog").css("display", "none");
422
+        $("#remoteVideos").css("padding", "0px 0px 18px 0px");
423
+        $("#remoteVideos").css("right", "0px");
424
+        messageHandler.disableNotifications();
425
+        $('body').popover("disable");
426
+//        $("[data-toggle=popover]").popover("disable");
427
+        JitsiPopover.enabled = false;
428
+    }
405 429
 
406 430
     document.title = interfaceConfig.APP_NAME;
407 431
 
408
-    $("#downloadlog").click(function (event) {
409
-        dump(event.target);
410
-    });
411
-
412
-    if(config.enableWelcomePage && window.location.pathname == "/" &&
413
-        (!window.localStorage.welcomePageDisabled ||
414
-            window.localStorage.welcomePageDisabled == "false")) {
415
-        $("#videoconference_page").hide();
416
-        if (!setupWelcomePage)
417
-            setupWelcomePage = require("./welcome_page/WelcomePage");
418
-        setupWelcomePage();
419 432
 
420
-        return;
421
-    }
422 433
 
423
-    $("#welcome_page").hide();
424 434
 
425
-    // Display notice message at the top of the toolbar
426
-    if (config.noticeMessage) {
427
-        $('#noticeText').text(config.noticeMessage);
428
-        $('#notice').css({display: 'block'});
429
-    }
430 435
 
431 436
     if(config.requireDisplayName) {
432 437
         var currentSettings = Settings.getSettings();
@@ -437,30 +442,33 @@ UI.start = function (init) {
437 442
 
438 443
     init();
439 444
 
440
-    toastr.options = {
441
-        "closeButton": true,
442
-        "debug": false,
443
-        "positionClass": "notification-bottom-right",
444
-        "onclick": null,
445
-        "showDuration": "300",
446
-        "hideDuration": "1000",
447
-        "timeOut": "2000",
448
-        "extendedTimeOut": "1000",
449
-        "showEasing": "swing",
450
-        "hideEasing": "linear",
451
-        "showMethod": "fadeIn",
452
-        "hideMethod": "fadeOut",
453
-        "reposition": function() {
454
-            if(PanelToggler.isVisible()) {
455
-                $("#toast-container").addClass("notification-bottom-right-center");
456
-            } else {
457
-                $("#toast-container").removeClass("notification-bottom-right-center");
458
-            }
459
-        },
460
-        "newestOnTop": false
461
-    };
445
+    if (!interfaceConfig.filmStripOnly) {
446
+        toastr.options = {
447
+            "closeButton": true,
448
+            "debug": false,
449
+            "positionClass": "notification-bottom-right",
450
+            "onclick": null,
451
+            "showDuration": "300",
452
+            "hideDuration": "1000",
453
+            "timeOut": "2000",
454
+            "extendedTimeOut": "1000",
455
+            "showEasing": "swing",
456
+            "hideEasing": "linear",
457
+            "showMethod": "fadeIn",
458
+            "hideMethod": "fadeOut",
459
+            "reposition": function () {
460
+                if (PanelToggler.isVisible()) {
461
+                    $("#toast-container").addClass("notification-bottom-right-center");
462
+                } else {
463
+                    $("#toast-container").removeClass("notification-bottom-right-center");
464
+                }
465
+            },
466
+            "newestOnTop": false
467
+        };
468
+
462 469
 
463
-    SettingsMenu.init();
470
+        SettingsMenu.init();
471
+    }
464 472
 
465 473
 };
466 474
 
@@ -533,6 +541,8 @@ function onLocalRoleChanged(jid, info, pres, isModerator) {
533 541
         Authentication.closeAuthenticationWindow();
534 542
         messageHandler.notify(null, "notify.me",
535 543
             'connected', "notify.moderator");
544
+
545
+        Toolbar.checkAutoRecord();
536 546
     }
537 547
 }
538 548
 
@@ -667,8 +677,8 @@ UI.inputDisplayNameHandler = function (value) {
667 677
     VideoLayout.inputDisplayNameHandler(value);
668 678
 };
669 679
 
670
-UI.getLargeVideoJid = function() {
671
-    return VideoLayout.getLargeVideoJid();
680
+UI.getLargeVideoResource = function () {
681
+    return VideoLayout.getLargeVideoResource();
672 682
 };
673 683
 
674 684
 UI.generateRoomName = function() {

+ 48
- 5
modules/UI/toolbars/Toolbar.js Прегледај датотеку

@@ -13,6 +13,7 @@ var AuthenticationEvents
13 13
 var roomUrl = null;
14 14
 var sharedKey = '';
15 15
 var UI = null;
16
+var recordingToaster = null;
16 17
 
17 18
 var buttonHandlers = {
18 19
     "toolbar_button_mute": function () {
@@ -122,8 +123,13 @@ function hangup() {
122 123
  * Starts or stops the recording for the conference.
123 124
  */
124 125
 
125
-function toggleRecording() {
126
+function toggleRecording(predefinedToken) {
126 127
     APP.xmpp.toggleRecording(function (callback) {
128
+        if (predefinedToken) {
129
+            callback(UIUtil.escapeHtml(predefinedToken));
130
+            return;
131
+        }
132
+
127 133
         var msg = APP.translation.generateTranslationHTML(
128 134
             "dialog.recordingToken");
129 135
         var token = APP.translation.translateString("dialog.token");
@@ -147,7 +153,7 @@ function toggleRecording() {
147 153
             function () { },
148 154
             ':input:first'
149 155
         );
150
-    }, Toolbar.setRecordingButtonState, Toolbar.setRecordingButtonState);
156
+    }, Toolbar.setRecordingButtonState);
151 157
 }
152 158
 
153 159
 /**
@@ -548,17 +554,54 @@ var Toolbar = (function (my) {
548 554
     };
549 555
 
550 556
     // Sets the state of the recording button
551
-    my.setRecordingButtonState = function (isRecording) {
557
+    my.setRecordingButtonState = function (recordingState) {
552 558
         var selector = $('#toolbar_button_record');
553
-        if (isRecording) {
559
+
560
+        if (recordingState === 'on') {
554 561
             selector.removeClass("icon-recEnable");
555 562
             selector.addClass("icon-recEnable active");
556
-        } else {
563
+
564
+            $("#largeVideo").toggleClass("videoMessageFilter", true);
565
+            var recordOnKey = "recording.on";
566
+            $('#videoConnectionMessage').attr("data-i18n", recordOnKey);
567
+            $('#videoConnectionMessage').text(APP.translation.translateString(recordOnKey));
568
+
569
+            setTimeout(function(){
570
+                $("#largeVideo").toggleClass("videoMessageFilter", false);
571
+                $('#videoConnectionMessage').css({display: "none"});
572
+            }, 1500);
573
+
574
+            recordingToaster = messageHandler.notify(null, "recording.toaster", null,
575
+                null, null, {timeOut: 0, closeButton: null, tapToDismiss: false});
576
+        } else if (recordingState === 'off') {
557 577
             selector.removeClass("icon-recEnable active");
558 578
             selector.addClass("icon-recEnable");
579
+
580
+            $("#largeVideo").toggleClass("videoMessageFilter", false);
581
+            $('#videoConnectionMessage').css({display: "none"});
582
+
583
+            if (recordingToaster)
584
+                messageHandler.remove(recordingToaster);
585
+
586
+        } else if (recordingState === 'pending') {
587
+            selector.removeClass("icon-recEnable active");
588
+            selector.addClass("icon-recEnable");
589
+
590
+            $("#largeVideo").toggleClass("videoMessageFilter", true);
591
+            var recordPendingKey = "recording.pending";
592
+            $('#videoConnectionMessage').attr("data-i18n", recordPendingKey);
593
+            $('#videoConnectionMessage').text(APP.translation.translateString(recordPendingKey));
594
+            $('#videoConnectionMessage').css({display: "block"});
559 595
         }
560 596
     };
561 597
 
598
+    // checks whether recording is enabled and whether we have params to start automatically recording
599
+    my.checkAutoRecord = function () {
600
+        if (config.enableRecording && config.autoRecord) {
601
+            toggleRecording(config.autoRecordToken);
602
+        }
603
+    }
604
+
562 605
     // Shows or hides SIP calls button
563 606
     my.showSipCallButton = function (show) {
564 607
         if (APP.xmpp.isSipGatewayEnabled() && show) {

+ 5
- 0
modules/UI/toolbars/ToolbarToggler.js Прегледај датотеку

@@ -53,6 +53,8 @@ var ToolbarToggler = {
53 53
      * Shows the main toolbar.
54 54
      */
55 55
     showToolbar: function () {
56
+        if (interfaceConfig.filmStripOnly)
57
+            return;
56 58
         var header = $("#header"),
57 59
             bottomToolbar = $("#bottomToolbar");
58 60
         if (!header.is(':visible') || !bottomToolbar.is(":visible")) {
@@ -88,6 +90,9 @@ var ToolbarToggler = {
88 90
      * @param isDock indicates what operation to perform
89 91
      */
90 92
     dockToolbar: function (isDock) {
93
+        if (interfaceConfig.filmStripOnly)
94
+            return;
95
+
91 96
         if (isDock) {
92 97
             // First make sure the toolbar is shown.
93 98
             if (!$('#header').is(':visible')) {

+ 4
- 0
modules/UI/util/JitsiPopover.js Прегледај датотеку

@@ -46,6 +46,8 @@ var JitsiPopover = (function () {
46 46
      * Shows the popover
47 47
      */
48 48
     JitsiPopover.prototype.show = function () {
49
+        if(!JitsiPopover.enabled)
50
+            return;
49 51
         this.createPopover();
50 52
         this.popoverShown = true;
51 53
     };
@@ -118,6 +120,8 @@ var JitsiPopover = (function () {
118 120
         this.createPopover();
119 121
     };
120 122
 
123
+    JitsiPopover.enabled = true;
124
+
121 125
     return JitsiPopover;
122 126
 })();
123 127
 

+ 41
- 1
modules/UI/util/MessageHandler.js Прегледај датотеку

@@ -1,4 +1,11 @@
1 1
 /* global $, APP, jQuery, toastr */
2
+
3
+/**
4
+ * Flag for enable/disable of the notifications.
5
+ * @type {boolean}
6
+ */
7
+var notificationsEnabled = true;
8
+
2 9
 var messageHandler = (function(my) {
3 10
 
4 11
     /**
@@ -172,8 +179,19 @@ var messageHandler = (function(my) {
172 179
         messageHandler.openMessageDialog(titleKey, msgKey);
173 180
     };
174 181
 
182
+    /**
183
+     * Displayes notification.
184
+     * @param displayName display name of the participant that is associated with the notification.
185
+     * @param displayNameKey the key from the language file for the display name.
186
+     * @param cls css class for the notification
187
+     * @param messageKey the key from the language file for the text of the message.
188
+     * @param messageArguments object with the arguments for the message.
189
+     * @param options object with language options.
190
+     */
175 191
     my.notify = function(displayName, displayNameKey,
176 192
                          cls, messageKey, messageArguments, options) {
193
+        if(!notificationsEnabled)
194
+            return;
177 195
         var displayNameSpan = '<span class="nickname" ';
178 196
         if (displayName) {
179 197
             displayNameSpan += ">" + displayName;
@@ -182,7 +200,7 @@ var messageHandler = (function(my) {
182 200
                 "'>" + APP.translation.translateString(displayNameKey);
183 201
         }
184 202
         displayNameSpan += "</span>";
185
-        toastr.info(
203
+        return toastr.info(
186 204
             displayNameSpan + '<br>' +
187 205
             '<span class=' + cls + ' data-i18n="' + messageKey + '"' +
188 206
                 (messageArguments?
@@ -193,6 +211,28 @@ var messageHandler = (function(my) {
193 211
             '</span>', null, options);
194 212
     };
195 213
 
214
+    /**
215
+     * Removes the toaster.
216
+     * @param toasterElement
217
+     */
218
+    my.remove = function(toasterElement) {
219
+        toasterElement.remove();
220
+    };
221
+
222
+    /**
223
+     * Disables notifications.
224
+     */
225
+    my.disableNotifications = function () {
226
+        notificationsEnabled = false;
227
+    };
228
+
229
+    /**
230
+     * Enables notifications.
231
+     */
232
+    my.enableNotifications = function () {
233
+        notificationsEnabled = true;
234
+    };
235
+
196 236
     return my;
197 237
 }(messageHandler || {}));
198 238
 

+ 9
- 13
modules/UI/videolayout/LargeVideo.js Прегледај датотеку

@@ -469,6 +469,14 @@ var LargeVideo = {
469 469
             largeVideoHeight,
470 470
             horizontalIndent, verticalIndent, animate);
471 471
     },
472
+    /**
473
+     * Resizes the large html elements.
474
+     * @param animate boolean property that indicates whether the resize should be animated or not.
475
+     * @param isChatVisible boolean property that indicates whether the chat area is displayed or not.
476
+     * If that parameter is null the method will check the chat pannel visibility.
477
+     * @param completeFunction a function to be called when the video space is resized
478
+     * @returns {*[]} array with the current width and height values of the largeVideo html element.
479
+     */
472 480
     resize: function (animate, isVisible, completeFunction) {
473 481
         if(!isEnabled)
474 482
             return;
@@ -481,18 +489,8 @@ var LargeVideo = {
481 489
         var top = availableHeight / 2 - avatarSize / 4 * 3;
482 490
         $('#activeSpeaker').css('top', top);
483 491
 
492
+        this.VideoLayout.resizeVideoSpace(animate, isVisible, completeFunction);
484 493
         if(animate) {
485
-            $('#videospace').animate({
486
-                    right: window.innerWidth - availableWidth,
487
-                    width: availableWidth,
488
-                    height: availableHeight
489
-                },
490
-                {
491
-                    queue: false,
492
-                    duration: 500,
493
-                    complete: completeFunction
494
-                });
495
-
496 494
             $('#largeVideoContainer').animate({
497 495
                     width: availableWidth,
498 496
                     height: availableHeight
@@ -502,8 +500,6 @@ var LargeVideo = {
502 500
                     duration: 500
503 501
                 });
504 502
         } else {
505
-            $('#videospace').width(availableWidth);
506
-            $('#videospace').height(availableHeight);
507 503
             $('#largeVideoContainer').width(availableWidth);
508 504
             $('#largeVideoContainer').height(availableHeight);
509 505
         }

+ 76
- 65
modules/UI/videolayout/RemoteVideo.js Прегледај датотеку

@@ -42,85 +42,96 @@ RemoteVideo.prototype.addRemoteVideoContainer = function() {
42 42
  * @param jid the jid indicating the video for which we're adding a menu.
43 43
  * @param parentElement the parent element where this menu will be added
44 44
  */
45
-RemoteVideo.prototype.addRemoteVideoMenu = function () {
46
-    var spanElement = document.createElement('span');
47
-    spanElement.className = 'remotevideomenu';
48 45
 
49
-    this.container.appendChild(spanElement);
46
+if (!interfaceConfig.filmStripOnly) {
47
+    RemoteVideo.prototype.addRemoteVideoMenu = function () {
48
+        var spanElement = document.createElement('span');
49
+        spanElement.className = 'remotevideomenu';
50 50
 
51
-    var menuElement = document.createElement('i');
52
-    menuElement.className = 'fa fa-angle-down';
53
-    menuElement.title = 'Remote user controls';
54
-    spanElement.appendChild(menuElement);
51
+        this.container.appendChild(spanElement);
55 52
 
53
+        var menuElement = document.createElement('i');
54
+        menuElement.className = 'fa fa-angle-down';
55
+        menuElement.title = 'Remote user controls';
56
+        spanElement.appendChild(menuElement);
56 57
 
57
-    var popupmenuElement = document.createElement('ul');
58
-    popupmenuElement.className = 'popupmenu';
59
-    popupmenuElement.id = 'remote_popupmenu_' + this.getResourceJid();
60
-    spanElement.appendChild(popupmenuElement);
61 58
 
62
-    var muteMenuItem = document.createElement('li');
63
-    var muteLinkItem = document.createElement('a');
59
+        var popupmenuElement = document.createElement('ul');
60
+        popupmenuElement.className = 'popupmenu';
61
+        popupmenuElement.id = 'remote_popupmenu_' + this.getResourceJid();
62
+        spanElement.appendChild(popupmenuElement);
64 63
 
65
-    var mutedIndicator = "<i style='float:left;' class='icon-mic-disabled'></i>";
64
+        var muteMenuItem = document.createElement('li');
65
+        var muteLinkItem = document.createElement('a');
66 66
 
67
-    if (!this.isMuted) {
68
-        muteLinkItem.innerHTML = mutedIndicator +
69
-            " <div style='width: 90px;margin-left: 20px;' data-i18n='videothumbnail.domute'></div>";
70
-        muteLinkItem.className = 'mutelink';
71
-    }
72
-    else {
73
-        muteLinkItem.innerHTML = mutedIndicator +
74
-            " <div style='width: 90px;margin-left: 20px;' data-i18n='videothumbnail.muted'></div>";
75
-        muteLinkItem.className = 'mutelink disabled';
76
-    }
77
-
78
-    var self = this;
79
-    muteLinkItem.onclick = function(){
80
-        if ($(this).attr('disabled') != undefined) {
81
-            event.preventDefault();
82
-        }
83
-        var isMute = self.isMuted == true;
84
-        APP.xmpp.setMute(self.peerJid, !isMute);
85
-
86
-        popupmenuElement.setAttribute('style', 'display:none;');
67
+        var mutedIndicator = "<i style='float:left;' " +
68
+            "class='icon-mic-disabled'></i>";
87 69
 
88
-        if (isMute) {
89
-            this.innerHTML = mutedIndicator +
90
-                " <div style='width: 90px;margin-left: 20px;' data-i18n='videothumbnail.muted'></div>";
91
-            this.className = 'mutelink disabled';
70
+        if (!this.isMuted) {
71
+            muteLinkItem.innerHTML = mutedIndicator +
72
+                " <div style='width: 90px;margin-left: 20px;' " +
73
+                "data-i18n='videothumbnail.domute'></div>";
74
+            muteLinkItem.className = 'mutelink';
92 75
         }
93 76
         else {
94
-            this.innerHTML = mutedIndicator +
95
-                " <div style='width: 90px;margin-left: 20px;' data-i18n='videothumbnail.domute'></div>";
96
-            this.className = 'mutelink';
77
+            muteLinkItem.innerHTML = mutedIndicator +
78
+                " <div style='width: 90px;margin-left: 20px;' " +
79
+                "data-i18n='videothumbnail.muted'></div>";
80
+            muteLinkItem.className = 'mutelink disabled';
97 81
         }
98
-    };
99
-
100
-    muteMenuItem.appendChild(muteLinkItem);
101
-    popupmenuElement.appendChild(muteMenuItem);
102 82
 
103
-    var ejectIndicator = "<i style='float:left;' class='fa fa-eject'></i>";
104
-
105
-    var ejectMenuItem = document.createElement('li');
106
-    var ejectLinkItem = document.createElement('a');
107
-    var ejectText = "<div style='width: 90px;margin-left: 20px;' data-i18n='videothumbnail.kick'>&nbsp;</div>";
108
-    ejectLinkItem.innerHTML = ejectIndicator + ' ' + ejectText;
109
-    ejectLinkItem.onclick = function(){
110
-        APP.xmpp.eject(self.peerJid);
111
-        popupmenuElement.setAttribute('style', 'display:none;');
83
+        var self = this;
84
+        muteLinkItem.onclick = function(){
85
+            if ($(this).attr('disabled') != undefined) {
86
+                event.preventDefault();
87
+            }
88
+            var isMute = self.isMuted == true;
89
+            APP.xmpp.setMute(self.peerJid, !isMute);
90
+
91
+            popupmenuElement.setAttribute('style', 'display:none;');
92
+
93
+            if (isMute) {
94
+                this.innerHTML = mutedIndicator +
95
+                    " <div style='width: 90px;margin-left: 20px;' " +
96
+                    "data-i18n='videothumbnail.muted'></div>";
97
+                this.className = 'mutelink disabled';
98
+            }
99
+            else {
100
+                this.innerHTML = mutedIndicator +
101
+                    " <div style='width: 90px;margin-left: 20px;' " +
102
+                    "data-i18n='videothumbnail.domute'></div>";
103
+                this.className = 'mutelink';
104
+            }
105
+        };
106
+
107
+        muteMenuItem.appendChild(muteLinkItem);
108
+        popupmenuElement.appendChild(muteMenuItem);
109
+
110
+        var ejectIndicator = "<i style='float:left;' class='fa fa-eject'></i>";
111
+
112
+        var ejectMenuItem = document.createElement('li');
113
+        var ejectLinkItem = document.createElement('a');
114
+        var ejectText = "<div style='width: 90px;margin-left: 20px;' " +
115
+            "data-i18n='videothumbnail.kick'>&nbsp;</div>";
116
+        ejectLinkItem.innerHTML = ejectIndicator + ' ' + ejectText;
117
+        ejectLinkItem.onclick = function(){
118
+            APP.xmpp.eject(self.peerJid);
119
+            popupmenuElement.setAttribute('style', 'display:none;');
120
+        };
121
+
122
+        ejectMenuItem.appendChild(ejectLinkItem);
123
+        popupmenuElement.appendChild(ejectMenuItem);
124
+
125
+        var paddingSpan = document.createElement('span');
126
+        paddingSpan.className = 'popupmenuPadding';
127
+        popupmenuElement.appendChild(paddingSpan);
128
+        APP.translation.translateElement(
129
+            $("#" + popupmenuElement.id + " > li > a > div"));
112 130
     };
113 131
 
114
-    ejectMenuItem.appendChild(ejectLinkItem);
115
-    popupmenuElement.appendChild(ejectMenuItem);
116
-
117
-    var paddingSpan = document.createElement('span');
118
-    paddingSpan.className = 'popupmenuPadding';
119
-    popupmenuElement.appendChild(paddingSpan);
120
-    APP.translation.translateElement(
121
-        $("#" + popupmenuElement.id + " > li > a > div"));
122
-};
123
-
132
+} else {
133
+    RemoteVideo.prototype.addRemoteVideoMenu = function() {}
134
+}
124 135
 
125 136
 /**
126 137
  * Removes the remote stream element corresponding to the given stream and

+ 49
- 8
modules/UI/videolayout/VideoLayout.js Прегледај датотеку

@@ -3,6 +3,7 @@ var AudioLevels = require("../audio_levels/AudioLevels");
3 3
 var ContactList = require("../side_pannels/contactlist/ContactList");
4 4
 var MediaStreamType = require("../../../service/RTC/MediaStreamTypes");
5 5
 var UIEvents = require("../../../service/UI/UIEvents");
6
+var UIUtil = require("../util/UIUtil");
6 7
 
7 8
 var RTC = require("../../RTC/RTC");
8 9
 var RTCBrowserType = require('../../RTC/RTCBrowserType');
@@ -11,6 +12,7 @@ var RemoteVideo = require("./RemoteVideo");
11 12
 var LargeVideo = require("./LargeVideo");
12 13
 var LocalVideo = require("./LocalVideo");
13 14
 
15
+
14 16
 var remoteVideos = {};
15 17
 var remoteVideoTypes = {};
16 18
 var localVideoThumbnail = null;
@@ -34,8 +36,12 @@ var VideoLayout = (function (my) {
34 36
     my.init = function (emitter) {
35 37
         eventEmitter = emitter;
36 38
         localVideoThumbnail = new LocalVideo(VideoLayout);
39
+        if (interfaceConfig.filmStripOnly) {
40
+            LargeVideo.disable();
41
+        } else {
42
+            LargeVideo.init(VideoLayout, emitter);
43
+        }
37 44
 
38
-        LargeVideo.init(VideoLayout, emitter);
39 45
         VideoLayout.resizeLargeVideoContainer();
40 46
 
41 47
     };
@@ -164,7 +170,7 @@ var VideoLayout = (function (my) {
164 170
         }
165 171
     };
166 172
 
167
-    my.getLargeVideoJid = function () {
173
+    my.getLargeVideoResource = function () {
168 174
         return LargeVideo.getResourceJid();
169 175
     };
170 176
 
@@ -192,7 +198,7 @@ var VideoLayout = (function (my) {
192 198
                                           resourceJid) {
193 199
         if(focusedVideoResourceJid) {
194 200
             var oldSmallVideo = VideoLayout.getSmallVideo(focusedVideoResourceJid);
195
-            if(oldSmallVideo)
201
+            if (oldSmallVideo && !interfaceConfig.filmStripOnly)
196 202
                 oldSmallVideo.focus(false);
197 203
         }
198 204
 
@@ -219,7 +225,7 @@ var VideoLayout = (function (my) {
219 225
 
220 226
         // Update focused/pinned interface.
221 227
         if (resourceJid) {
222
-            if(smallVideo)
228
+            if (smallVideo && !interfaceConfig.filmStripOnly)
223 229
                 smallVideo.focus(true);
224 230
 
225 231
             if (!noPinnedEndpointChangedEvent) {
@@ -354,7 +360,11 @@ var VideoLayout = (function (my) {
354 360
      * Resizes the large video container.
355 361
      */
356 362
     my.resizeLargeVideoContainer = function () {
357
-        LargeVideo.resize();
363
+        if(LargeVideo.isEnabled()) {
364
+            LargeVideo.resize();
365
+        } else {
366
+            VideoLayout.resizeVideoSpace();
367
+        }
358 368
         VideoLayout.resizeThumbnails();
359 369
         LargeVideo.position();
360 370
     };
@@ -373,7 +383,7 @@ var VideoLayout = (function (my) {
373 383
 
374 384
         if(animate) {
375 385
             $('#remoteVideos').animate({
376
-                    height: height
386
+                    height: height + 2 // adds 2 px because of small video 1px border
377 387
                 },
378 388
                 {
379 389
                     queue: false,
@@ -398,7 +408,7 @@ var VideoLayout = (function (my) {
398 408
         } else {
399 409
             // size videos so that while keeping AR and max height, we have a
400 410
             // nice fit
401
-            $('#remoteVideos').height(height);
411
+            $('#remoteVideos').height(height + 2);// adds 2 px because of small video 1px border
402 412
             $('#remoteVideos>span').width(width);
403 413
             $('#remoteVideos>span').height(height);
404 414
 
@@ -431,7 +441,7 @@ var VideoLayout = (function (my) {
431 441
        var availableWidth = availableWinWidth / numvids;
432 442
        var aspectRatio = 16.0 / 9.0;
433 443
        var maxHeight = Math.min(160, availableHeight);
434
-       availableHeight = Math.min(maxHeight, availableWidth / aspectRatio);
444
+       availableHeight = Math.min(maxHeight, availableWidth / aspectRatio, window.innerHeight - 18);
435 445
        if (availableHeight < availableWidth / aspectRatio) {
436 446
            availableWidth = Math.floor(availableHeight * aspectRatio);
437 447
        }
@@ -886,6 +896,37 @@ var VideoLayout = (function (my) {
886 896
         VideoLayout.resizeThumbnails(true);
887 897
     };
888 898
 
899
+    /**
900
+     * Resizes the #videospace html element
901
+     * @param animate boolean property that indicates whether the resize should be animated or not.
902
+     * @param isChatVisible boolean property that indicates whether the chat area is displayed or not.
903
+     * If that parameter is null the method will check the chat pannel visibility.
904
+     * @param completeFunction a function to be called when the video space is resized
905
+     */
906
+    my.resizeVideoSpace = function (animate, isChatVisible, completeFunction) {
907
+        var availableHeight = window.innerHeight;
908
+        var availableWidth = UIUtil.getAvailableVideoWidth(isChatVisible);
909
+
910
+        if (availableWidth < 0 || availableHeight < 0) return;
911
+
912
+        if(animate) {
913
+            $('#videospace').animate({
914
+                    right: window.innerWidth - availableWidth,
915
+                    width: availableWidth,
916
+                    height: availableHeight
917
+                },
918
+                {
919
+                    queue: false,
920
+                    duration: 500,
921
+                    complete: completeFunction
922
+                });
923
+        } else {
924
+            $('#videospace').width(availableWidth);
925
+            $('#videospace').height(availableHeight);
926
+        }
927
+
928
+    };
929
+
889 930
     my.getSmallVideo = function (resourceJid) {
890 931
         if(resourceJid == APP.xmpp.myResource()) {
891 932
             return localVideoThumbnail;

+ 19
- 10
modules/URLProcessor/URLProcessor.js Прегледај датотеку

@@ -1,4 +1,4 @@
1
-/* global $, $iq, config */
1
+/* global $, $iq, config, interfaceConfig */
2 2
 var params = {};
3 3
 function getConfigParamsFromUrl() {
4 4
     if(!location.hash)
@@ -17,22 +17,31 @@ params = getConfigParamsFromUrl();
17 17
 
18 18
 var URLProcessor = {
19 19
     setConfigParametersFromUrl: function () {
20
-        for(var k in params)
21
-        {
22
-            if(typeof k !== "string" || k.indexOf("config.") === -1)
20
+        for(var key in params) {
21
+            if(typeof key !== "string")
23 22
                 continue;
24 23
 
25
-            var v = params[k];
26
-            var confKey = k.substr(7);
27
-            if(config[confKey] && typeof config[confKey] !== typeof v)
24
+            var confObj = null, confKey;
25
+            if (key.indexOf("config.") === 0) {
26
+                confObj = config;
27
+                confKey = key.substr("config.".length);
28
+            } else if (key.indexOf("interfaceConfig.") === 0) {
29
+                confObj = interfaceConfig;
30
+                confKey = key.substr("interfaceConfig.".length);
31
+            }
32
+
33
+            if (!confObj)
34
+                continue;
35
+
36
+            var value = params[key];
37
+            if (confObj[confKey] && typeof confObj[confKey] !== typeof value)
28 38
             {
29
-                console.warn("The type of " + k +
39
+                console.warn("The type of " + key +
30 40
                     " is wrong. That parameter won't be updated in config.js.");
31 41
                 continue;
32 42
             }
33 43
 
34
-            config[confKey] = v;
35
-
44
+            confObj[confKey] = value;
36 45
         }
37 46
 
38 47
     }

+ 86
- 1413
modules/xmpp/JingleSession.js
Разлика између датотеке није приказан због своје велике величине
Прегледај датотеку


+ 1444
- 0
modules/xmpp/JingleSessionPC.js
Разлика између датотеке није приказан због своје велике величине
Прегледај датотеку


modules/xmpp/VideoSSRCHack.js → modules/xmpp/LocalSSRCReplacement.js Прегледај датотеку

@@ -1,8 +1,21 @@
1 1
 /* global $ */
2 2
 
3 3
 /*
4
- The purpose of this hack is to re-use SSRC of first video stream ever created
5
- for any video streams created later on. In order to do that this hack:
4
+ Here we do modifications of local video SSRCs. There are 2 situations we have
5
+ to handle:
6
+
7
+ 1. We generate SSRC for local recvonly video stream. This is the case when we
8
+    have no local camera and it is not generated automatically, but SSRC=1 is
9
+    used implicitly. If that happens RTCP packets will be dropped by the JVB
10
+    and we won't be able to request video key frames correctly.
11
+
12
+ 2. A hack to re-use SSRC of the first video stream for any new stream created
13
+    in future. It turned out that Chrome may keep on using the SSRC of removed
14
+    video stream in RTCP even though a new one has been created. So we just
15
+    want to avoid that by re-using it. Jingle 'source-remove'/'source-add'
16
+    notifications are blocked once first video SSRC is advertised to the focus.
17
+
18
+ What this hack does:
6 19
 
7 20
  1. Stores the SSRC of the first video stream created by
8 21
    a) scanning Jingle session-accept/session-invite for existing video SSRC
@@ -19,12 +32,32 @@
19 32
  */
20 33
 
21 34
 var SDP = require('./SDP');
35
+var RTCBrowserType = require('../RTC/RTCBrowserType');
36
+
37
+/**
38
+ * The hack is enabled on all browsers except FF by default
39
+ * FIXME finish the hack once removeStream method is implemented in FF
40
+ * @type {boolean}
41
+ */
42
+var isEnabled = !RTCBrowserType.isFirefox();
22 43
 
23 44
 /**
24 45
  * Stored SSRC of local video stream.
25 46
  */
26 47
 var localVideoSSRC;
27 48
 
49
+/**
50
+ * SSRC used for recvonly video stream when we have no local camera.
51
+ * This is in order to tell Chrome what SSRC should be used in RTCP requests
52
+ * instead of 1.
53
+ */
54
+var localRecvOnlySSRC;
55
+
56
+/**
57
+ * cname for <tt>localRecvOnlySSRC</tt>
58
+ */
59
+var localRecvOnlyCName;
60
+
28 61
 /**
29 62
  * Method removes <source> element which describes <tt>localVideoSSRC</tt>
30 63
  * from given Jingle IQ.
@@ -36,16 +69,17 @@ var localVideoSSRC;
36 69
  *          other SSRCs left to be signaled after removing it.
37 70
  */
38 71
 var filterOutSource = function (modifyIq, actionName) {
72
+    var modifyIqTree = $(modifyIq.tree());
73
+
39 74
     if (!localVideoSSRC)
40
-        return modifyIq;
75
+        return modifyIqTree[0];
41 76
 
42
-    var modifyIqTree = $(modifyIq.tree());
43 77
     var videoSSRC = modifyIqTree.find(
44 78
         '>jingle>content[name="video"]' +
45 79
         '>description>source[ssrc="' + localVideoSSRC + '"]');
46 80
 
47 81
     if (!videoSSRC.length) {
48
-        return modifyIqTree;
82
+        return modifyIqTree[0];
49 83
     }
50 84
 
51 85
     console.info(
@@ -55,7 +89,7 @@ var filterOutSource = function (modifyIq, actionName) {
55 89
 
56 90
     // Check if any sources still left to be added/removed
57 91
     if (modifyIqTree.find('>jingle>content>description>source').length) {
58
-        return modifyIqTree;
92
+        return modifyIqTree[0];
59 93
     } else {
60 94
         return null;
61 95
     }
@@ -70,21 +104,41 @@ var storeLocalVideoSSRC = function (jingleIq) {
70 104
         $(jingleIq.tree())
71 105
             .find('>jingle>content[name="video"]>description>source');
72 106
 
73
-    console.info('Video desc: ', videoSSRCs);
74
-    if (!videoSSRCs.length)
75
-        return;
76
-
77
-    var ssrc = videoSSRCs.attr('ssrc');
78
-    if (ssrc) {
79
-        localVideoSSRC = ssrc;
80
-        console.info(
81
-            'Stored local video SSRC for future re-use: ' + localVideoSSRC);
82
-    } else {
83
-        console.error('No "ssrc" attribute present in <source> element');
84
-    }
107
+    videoSSRCs.each(function (idx, ssrcElem) {
108
+        if (localVideoSSRC)
109
+            return;
110
+        // We consider SSRC real only if it has msid attribute
111
+        // recvonly streams in FF do not have it as well as local SSRCs
112
+        // we generate for recvonly streams in Chrome
113
+        var ssrSel = $(ssrcElem);
114
+        var msid = ssrSel.find('>parameter[name="msid"]');
115
+        if (msid.length) {
116
+            var ssrcVal = ssrSel.attr('ssrc');
117
+            if (ssrcVal) {
118
+                localVideoSSRC = ssrcVal;
119
+                console.info('Stored local video SSRC' +
120
+                             ' for future re-use: ' + localVideoSSRC);
121
+            }
122
+        }
123
+    });
85 124
 };
86 125
 
87
-var LocalVideoSSRCHack = {
126
+/**
127
+ * Generates new SSRC for local video recvonly stream.
128
+ * FIXME what about eventual SSRC collision ?
129
+ */
130
+function generateRecvonlySSRC() {
131
+    //
132
+    localRecvOnlySSRC =
133
+        Math.random().toString(10).substring(2, 11);
134
+    localRecvOnlyCName =
135
+        Math.random().toString(36).substring(2);
136
+    console.info(
137
+        "Generated local recvonly SSRC: " + localRecvOnlySSRC +
138
+        ", cname: " + localRecvOnlyCName);
139
+}
140
+
141
+var LocalSSRCReplacement = {
88 142
     /**
89 143
      * Method must be called before 'session-initiate' or 'session-invite' is
90 144
      * sent. Scans the IQ for local video SSRC and stores it if detected.
@@ -93,6 +147,9 @@ var LocalVideoSSRCHack = {
93 147
      *        which will be scanned for local video SSRC.
94 148
      */
95 149
     processSessionInit: function (sessionInit) {
150
+        if (!isEnabled)
151
+            return;
152
+
96 153
         if (localVideoSSRC) {
97 154
             console.error("Local SSRC stored already: " + localVideoSSRC);
98 155
             return;
@@ -108,6 +165,9 @@ var LocalVideoSSRCHack = {
108 165
      * @returns modified <tt>localDescription</tt> object.
109 166
      */
110 167
     mungeLocalVideoSSRC: function (localDescription) {
168
+        if (!isEnabled)
169
+            return localDescription;
170
+
111 171
         // IF we have local video SSRC stored make sure it is replaced
112 172
         // with old SSRC
113 173
         if (localVideoSSRC) {
@@ -129,6 +189,25 @@ var LocalVideoSSRCHack = {
129 189
                         new RegExp('a=ssrc:' + newSSRC, 'g'),
130 190
                         'a=ssrc:' + localVideoSSRC);
131 191
             }
192
+        } else {
193
+            // Make sure we have any SSRC for recvonly video stream
194
+            var sdp = new SDP(localDescription.sdp);
195
+
196
+            if (sdp.media[1] && sdp.media[1].indexOf('a=ssrc:') === -1 &&
197
+                sdp.media[1].indexOf('a=recvonly') !== -1) {
198
+
199
+                if (!localRecvOnlySSRC) {
200
+                    generateRecvonlySSRC();
201
+                }
202
+
203
+                console.info('No SSRC in video recvonly stream' +
204
+                             ' - adding SSRC: ' + localRecvOnlySSRC);
205
+
206
+                sdp.media[1] += 'a=ssrc:' + localRecvOnlySSRC +
207
+                                ' cname:' + localRecvOnlyCName + '\r\n';
208
+
209
+                localDescription.sdp = sdp.session + sdp.media.join('');
210
+            }
132 211
         }
133 212
         return localDescription;
134 213
     },
@@ -143,6 +222,9 @@ var LocalVideoSSRCHack = {
143 222
      *          a Strophe IQ Builder instance, but DOM element tree.
144 223
      */
145 224
     processSourceAdd: function (sourceAdd) {
225
+        if (!isEnabled)
226
+            return sourceAdd;
227
+
146 228
         if (!localVideoSSRC) {
147 229
             // Store local SSRC if available
148 230
             storeLocalVideoSSRC(sourceAdd);
@@ -162,8 +244,20 @@ var LocalVideoSSRCHack = {
162 244
      *          a Strophe IQ Builder instance, but DOM element tree.
163 245
      */
164 246
     processSourceRemove: function (sourceRemove) {
247
+        if (!isEnabled)
248
+            return sourceRemove;
249
+
165 250
         return filterOutSource(sourceRemove, 'source-remove');
251
+    },
252
+
253
+    /**
254
+     * Turns the hack on or off
255
+     * @param enabled <tt>true</tt> to enable the hack or <tt>false</tt>
256
+     *                to disable it
257
+     */
258
+    setEnabled: function (enabled) {
259
+        isEnabled = enabled;
166 260
     }
167 261
 };
168 262
 
169
-module.exports = LocalVideoSSRCHack;
263
+module.exports = LocalSSRCReplacement;

+ 4
- 4
modules/xmpp/TraceablePeerConnection.js Прегледај датотеку

@@ -1,7 +1,7 @@
1 1
 var RTC = require('../RTC/RTC');
2 2
 var RTCBrowserType = require("../RTC/RTCBrowserType.js");
3 3
 var XMPPEvents = require("../../service/xmpp/XMPPEvents");
4
-var VideoSSRCHack = require("./VideoSSRCHack");
4
+var SSRCReplacement = require("./LocalSSRCReplacement");
5 5
 
6 6
 function TraceablePeerConnection(ice_config, constraints, session) {
7 7
     var self = this;
@@ -213,7 +213,7 @@ if (TraceablePeerConnection.prototype.__defineGetter__ !== undefined) {
213 213
         function() {
214 214
             var desc = this.peerconnection.localDescription;
215 215
 
216
-            desc = VideoSSRCHack.mungeLocalVideoSSRC(desc);
216
+            desc = SSRCReplacement.mungeLocalVideoSSRC(desc);
217 217
             
218 218
             this.trace('getLocalDescription::preTransform', dumpSDP(desc));
219 219
 
@@ -372,7 +372,7 @@ TraceablePeerConnection.prototype.createOffer
372 372
                 self.trace('createOfferOnSuccess::postTransform (Plan B)', dumpSDP(offer));
373 373
             }
374 374
 
375
-            offer = VideoSSRCHack.mungeLocalVideoSSRC(offer);
375
+            offer = SSRCReplacement.mungeLocalVideoSSRC(offer);
376 376
 
377 377
             if (config.enableSimulcast && self.simulcast.isSupported()) {
378 378
                 offer = self.simulcast.mungeLocalDescription(offer);
@@ -402,7 +402,7 @@ TraceablePeerConnection.prototype.createAnswer
402 402
             }
403 403
 
404 404
             // munge local video SSRC
405
-            answer = VideoSSRCHack.mungeLocalVideoSSRC(answer);
405
+            answer = SSRCReplacement.mungeLocalVideoSSRC(answer);
406 406
 
407 407
             if (config.enableSimulcast && self.simulcast.isSupported()) {
408 408
                 answer = self.simulcast.mungeLocalDescription(answer);

+ 26
- 12
modules/xmpp/recording.js Прегледај датотеку

@@ -19,6 +19,12 @@ var useJirecon = (typeof config.hosts.jirecon != "undefined");
19 19
  */
20 20
 var jireconRid = null;
21 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
+
22 28
 function setRecordingToken(token) {
23 29
     recordingToken = token;
24 30
 }
@@ -30,9 +36,9 @@ function setRecordingJirecon(state, token, callback, connection) {
30 36
 
31 37
     var iq = $iq({to: config.hosts.jirecon, type: 'set'})
32 38
         .c('recording', {xmlns: 'http://jitsi.org/protocol/jirecon',
33
-            action: state ? 'start' : 'stop',
39
+            action: (state === 'on') ? 'start' : 'stop',
34 40
             mucjid: connection.emuc.roomjid});
35
-    if (!state){
41
+    if (state === 'off'){
36 42
         iq.attrs({rid: jireconRid});
37 43
     }
38 44
 
@@ -44,10 +50,10 @@ function setRecordingJirecon(state, token, callback, connection) {
44 50
             // TODO wait for an IQ with the real status, since this is
45 51
             // provisional?
46 52
             jireconRid = $(result).find('recording').attr('rid');
47
-            console.log('Recording ' + (state ? 'started' : 'stopped') +
53
+            console.log('Recording ' + ((state === 'on') ? 'started' : 'stopped') +
48 54
                 '(jirecon)' + result);
49 55
             recordingEnabled = state;
50
-            if (!state){
56
+            if (state === 'off'){
51 57
                 jireconRid = null;
52 58
             }
53 59
 
@@ -73,10 +79,19 @@ function setRecordingColibri(state, token, callback, connection) {
73 79
         function (result) {
74 80
             console.log('Set recording "', state, '". Result:', result);
75 81
             var recordingElem = $(result).find('>conference>recording');
76
-            var newState = ('true' === recordingElem.attr('state'));
82
+            var newState = recordingElem.attr('state');
77 83
 
78 84
             recordingEnabled = newState;
79 85
             callback(newState);
86
+
87
+            if (newState === 'pending' && recordingStateChangeCallback == null) {
88
+                recordingStateChangeCallback = callback;
89
+                connection.addHandler(function(iq){
90
+                    var state = $(iq).find('recording').attr('state');
91
+                    if (state)
92
+                        recordingStateChangeCallback(state);
93
+                }, 'http://jitsi.org/protocol/colibri', 'iq', null, null, null);
94
+            }
80 95
         },
81 96
         function (error) {
82 97
             console.warn(error);
@@ -94,8 +109,7 @@ function setRecording(state, token, callback, connection) {
94 109
 }
95 110
 
96 111
 var Recording = {
97
-    toggleRecording: function (tokenEmptyCallback,
98
-                               startingCallback, startedCallback, connection) {
112
+    toggleRecording: function (tokenEmptyCallback, recordingStateChangeCallback, connection) {
99 113
         if (!Moderator.isModerator()) {
100 114
             console.log(
101 115
                     'non-focus, or conference not yet organized:' +
@@ -108,16 +122,16 @@ var Recording = {
108 122
         if (!recordingToken && !useJirecon) {
109 123
             tokenEmptyCallback(function (value) {
110 124
                 setRecordingToken(value);
111
-                self.toggleRecording(tokenEmptyCallback,
112
-                    startingCallback, startedCallback, connection);
125
+                self.toggleRecording(tokenEmptyCallback, recordingStateChangeCallback, connection);
113 126
             });
114 127
 
115 128
             return;
116 129
         }
117 130
 
118 131
         var oldState = recordingEnabled;
119
-        startingCallback(!oldState);
120
-        setRecording(!oldState,
132
+        var newState = (oldState === 'off' || !oldState) ? 'on' : 'off';
133
+
134
+        setRecording(newState,
121 135
             recordingToken,
122 136
             function (state) {
123 137
                 console.log("New recording state: ", state);
@@ -143,7 +157,7 @@ var Recording = {
143 157
                     // have been wrong
144 158
                     setRecordingToken(null);
145 159
                 }
146
-                startedCallback(state);
160
+                recordingStateChangeCallback(state);
147 161
 
148 162
             },
149 163
             connection

+ 17
- 23
modules/xmpp/strophe.jingle.js Прегледај датотеку

@@ -1,27 +1,11 @@
1 1
 /* jshint -W117 */
2 2
 
3
-var JingleSession = require("./JingleSession");
3
+var JingleSession = require("./JingleSessionPC");
4 4
 var XMPPEvents = require("../../service/xmpp/XMPPEvents");
5 5
 var RTCBrowserType = require("../RTC/RTCBrowserType");
6 6
 
7 7
 
8 8
 module.exports = function(XMPP, eventEmitter) {
9
-    function CallIncomingJingle(sid, connection) {
10
-        var sess = connection.jingle.sessions[sid];
11
-
12
-        // TODO: do we check activecall == null?
13
-        connection.jingle.activecall = sess;
14
-
15
-        eventEmitter.emit(XMPPEvents.CALL_INCOMING, sess);
16
-
17
-        // TODO: check affiliation and/or role
18
-        console.log('emuc data for', sess.peerjid, connection.emuc.members[sess.peerjid]);
19
-        sess.usedrip = true; // not-so-naive trickle ice
20
-        sess.sendAnswer();
21
-        sess.accept();
22
-
23
-    }
24
-
25 9
     Strophe.addConnectionPlugin('jingle', {
26 10
         connection: null,
27 11
         sessions: {},
@@ -126,20 +110,30 @@ module.exports = function(XMPP, eventEmitter) {
126 110
                     sess.pc_constraints = this.pc_constraints;
127 111
                     sess.ice_config = this.ice_config;
128 112
 
129
-                    sess.initiate(fromJid, false);
113
+                    sess.initialize(fromJid, false);
130 114
                     // FIXME: setRemoteDescription should only be done when this call is to be accepted
131
-                    sess.setRemoteDescription($(iq).find('>jingle'), 'offer');
115
+                    sess.setOffer($(iq).find('>jingle'));
132 116
 
133 117
                     this.sessions[sess.sid] = sess;
134 118
                     this.jid2session[sess.peerjid] = sess;
135 119
 
136 120
                     // the callback should either
137 121
                     // .sendAnswer and .accept
138
-                    // or .sendTerminate -- not necessarily synchronus
139
-                    CallIncomingJingle(sess.sid, this.connection);
122
+                    // or .sendTerminate -- not necessarily synchronous
123
+
124
+                    // TODO: do we check activecall == null?
125
+                    this.connection.jingle.activecall = sess;
126
+
127
+                    eventEmitter.emit(XMPPEvents.CALL_INCOMING, sess);
128
+
129
+                    // TODO: check affiliation and/or role
130
+                    console.log('emuc data for', sess.peerjid,
131
+                        this.connection.emuc.members[sess.peerjid]);
132
+                    sess.sendAnswer();
133
+                    sess.accept();
140 134
                     break;
141 135
                 case 'session-accept':
142
-                    sess.setRemoteDescription($(iq).find('>jingle'), 'answer');
136
+                    sess.setAnswer($(iq).find('>jingle'));
143 137
                     sess.accept();
144 138
                     $(document).trigger('callaccepted.jingle', [sess.sid]);
145 139
                     break;
@@ -206,7 +200,7 @@ module.exports = function(XMPP, eventEmitter) {
206 200
             sess.pc_constraints = this.pc_constraints;
207 201
             sess.ice_config = this.ice_config;
208 202
 
209
-            sess.initiate(peerjid, true);
203
+            sess.initialize(peerjid, true);
210 204
             this.sessions[sess.sid] = sess;
211 205
             this.jid2session[sess.peerjid] = sess;
212 206
             sess.sendOffer();

+ 2
- 2
modules/xmpp/xmpp.js Прегледај датотеку

@@ -434,9 +434,9 @@ var XMPP = {
434 434
         return true;
435 435
     },
436 436
     toggleRecording: function (tokenEmptyCallback,
437
-                               startingCallback, startedCallback) {
437
+                               recordingStateChangeCallback) {
438 438
         Recording.toggleRecording(tokenEmptyCallback,
439
-            startingCallback, startedCallback, connection);
439
+            recordingStateChangeCallback, connection);
440 440
     },
441 441
     addToPresence: function (name, value, dontSend) {
442 442
         switch (name) {

Loading…
Откажи
Сачувај