Browse Source

Extends the follow-me feature by adding the possibility to follow the pinned participant, the shared video and the shared document. Adds the possibility to enable disable follow-me from the settings panel. Some other small fixes throughout the UI.

j8
yanas 9 years ago
parent
commit
cc761700fe

+ 16
- 2
conference.js View File

@@ -31,6 +31,8 @@ const Commands = {
31 31
     SHARED_VIDEO: "shared-video"
32 32
 };
33 33
 
34
+import {VIDEO_CONTAINER_TYPE} from "./modules/UI/videolayout/LargeVideo";
35
+
34 36
 /**
35 37
  * Open Connection. When authentication failed it shows auth dialog.
36 38
  * @param roomName the room name to use
@@ -1030,8 +1032,20 @@ export default {
1030 1032
         APP.UI.addListener(UIEvents.SELECTED_ENDPOINT, (id) => {
1031 1033
             room.selectParticipant(id);
1032 1034
         });
1033
-        APP.UI.addListener(UIEvents.PINNED_ENDPOINT, (id) => {
1034
-            room.pinParticipant(id);
1035
+
1036
+        APP.UI.addListener(UIEvents.PINNED_ENDPOINT, (smallVideo, isPinned) => {
1037
+            var smallVideoId = smallVideo.getId();
1038
+
1039
+            if (smallVideo.getVideoType() === VIDEO_CONTAINER_TYPE
1040
+                && !APP.conference.isLocalId(smallVideoId))
1041
+                if (isPinned)
1042
+                    room.pinParticipant(smallVideoId);
1043
+                // When the library starts supporting multiple pins we would
1044
+                // pass the isPinned parameter together with the identifier,
1045
+                // but currently we send null to indicate that we unpin the
1046
+                // last pinned.
1047
+                else
1048
+                    room.pinParticipant(null);
1035 1049
         });
1036 1050
 
1037 1051
         APP.UI.addListener(

+ 7
- 11
css/settingsmenu.css View File

@@ -43,29 +43,25 @@
43 43
     cursor: pointer;
44 44
 }
45 45
 
46
-
47
-#startMutedOptions {
46
+#startMutedOptions,
47
+#followMeOptions {
48 48
     padding-left: 10%;
49 49
     text-indent: -10%;
50
-
51 50
     margin-top: 10px;
52
-
53 51
     display: none; /* hide by default */
54
-
55 52
     /* clearfix */
56 53
     overflow: auto;
57 54
     zoom: 1;
58 55
 }
59 56
 
60
-#startAudioMuted {
61
-    width: 13px !important;
62
-}
63
-
64
-#startVideoMuted {
57
+#startAudioMuted,
58
+#startVideoMuted,
59
+#followMeCheckBox {
65 60
     width: 13px !important;
66 61
 }
67 62
 
68
-.startMutedLabel {
63
+.startMutedLabel,
64
+.followMeLabel {
69 65
     width: 94%;
70 66
     float: left;
71 67
     cursor: pointer;

+ 6
- 0
index.html View File

@@ -240,6 +240,12 @@
240 240
                     <select id="selectMic"></select>
241 241
                 </label>
242 242
             </div>
243
+            <div id="followMeOptions">
244
+                <label class = "followMeLabel">
245
+                    <input type="checkbox" id="followMeCheckBox">
246
+                    <span  data-i18n="settings.followMe"></span>
247
+                </label>
248
+            </div>
243 249
             <a id="downloadlog" data-container="body" data-toggle="popover" data-placement="right" data-i18n="[data-content]downloadlogs" ><i class="fa fa-cloud-download"></i></a>
244 250
         </div>
245 251
         <div class="feedbackButton">

+ 2
- 1
lang/main.json View File

@@ -86,7 +86,8 @@
86 86
         "startAudioMuted": "start without audio",
87 87
         "startVideoMuted": "start without video",
88 88
         "selectCamera": "select camera",
89
-        "selectMic": "select microphone"
89
+        "selectMic": "select microphone",
90
+        "followMe": "Enable follow me"
90 91
     },
91 92
     "videothumbnail":
92 93
     {

+ 181
- 49
modules/FollowMe.js View File

@@ -1,27 +1,29 @@
1
-/*                                                                              
2
- * Copyright @ 2015 Atlassian Pty Ltd                                           
3
- *                                                                              
4
- * Licensed under the Apache License, Version 2.0 (the "License");              
5
- * you may not use this file except in compliance with the License.             
6
- * You may obtain a copy of the License at                                      
7
- *                                                                              
8
- *     http://www.apache.org/licenses/LICENSE-2.0                               
9
- *                                                                              
10
- * Unless required by applicable law or agreed to in writing, software          
11
- * distributed under the License is distributed on an "AS IS" BASIS,            
12
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.     
13
- * See the License for the specific language governing permissions and          
14
- * limitations under the License.                                               
1
+/*
2
+ * Copyright @ 2015 Atlassian Pty Ltd
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ *     http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15 15
  */
16 16
 
17 17
 import UIEvents from '../service/UI/UIEvents';
18
+import VideoLayout from './UI/videolayout/VideoLayout';
19
+import FilmStrip from './UI/videolayout/FilmStrip';
18 20
 
19 21
 /**
20 22
  * The (name of the) command which transports the state (represented by
21 23
  * {State} for the local state at the time of this writing) of a {FollowMe}
22 24
  * (instance) between participants.
23 25
  */
24
-/* private */ const _COMMAND = "follow-me";
26
+const _COMMAND = "follow-me";
25 27
 
26 28
 /**
27 29
  * Represents the set of {FollowMe}-related states (properties and their
@@ -29,7 +31,7 @@ import UIEvents from '../service/UI/UIEvents';
29 31
  * will send {_COMMAND} whenever a property of {State} changes (if the local
30 32
  * participant is in her right to issue such a command, of course).
31 33
  */
32
-/* private */ class State {
34
+class State {
33 35
     /**
34 36
      * Initializes a new {State} instance.
35 37
      *
@@ -39,13 +41,13 @@ import UIEvents from '../service/UI/UIEvents';
39 41
      * the property, the old value of the property before the change, and the
40 42
      * new value of the property after the change.
41 43
      */
42
-    /* public */ constructor (propertyChangeCallback) {
43
-        /* private*/ this._propertyChangeCallback = propertyChangeCallback;
44
+    constructor (propertyChangeCallback) {
45
+        this._propertyChangeCallback = propertyChangeCallback;
44 46
     }
45 47
 
46
-    /* public */ get filmStripVisible () { return this._filmStripVisible; }
48
+    get filmStripVisible () { return this._filmStripVisible; }
47 49
 
48
-    /* public */ set filmStripVisible (b) {
50
+    set filmStripVisible (b) {
49 51
         var oldValue = this._filmStripVisible;
50 52
         if (oldValue !== b) {
51 53
             this._filmStripVisible = b;
@@ -53,6 +55,26 @@ import UIEvents from '../service/UI/UIEvents';
53 55
         }
54 56
     }
55 57
 
58
+    get nextOnStage() { return this._nextOnStage; }
59
+
60
+    set nextOnStage(id) {
61
+        var oldValue = this._nextOnStage;
62
+        if (oldValue !== id) {
63
+            this._nextOnStage = id;
64
+            this._firePropertyChange('nextOnStage', oldValue, id);
65
+        }
66
+    }
67
+
68
+    get sharedDocumentVisible () { return this._sharedDocumentVisible; }
69
+
70
+    set sharedDocumentVisible (b) {
71
+        var oldValue = this._sharedDocumentVisible;
72
+        if (oldValue !== b) {
73
+            this._sharedDocumentVisible = b;
74
+            this._firePropertyChange('sharedDocumentVisible', oldValue, b);
75
+        }
76
+    }
77
+
56 78
     /**
57 79
      * Invokes {_propertyChangeCallback} to notify it that {property} had its
58 80
      * value changed from {oldValue} to {newValue}.
@@ -62,7 +84,7 @@ import UIEvents from '../service/UI/UIEvents';
62 84
      * @param oldValue the value of {property} before the change
63 85
      * @param newValue the value of {property} after the change
64 86
      */
65
-    /* private */ _firePropertyChange (property, oldValue, newValue) {
87
+    _firePropertyChange (property, oldValue, newValue) {
66 88
         var propertyChangeCallback = this._propertyChangeCallback;
67 89
         if (propertyChangeCallback)
68 90
             propertyChangeCallback(property, oldValue, newValue);
@@ -74,9 +96,9 @@ import UIEvents from '../service/UI/UIEvents';
74 96
  * (partially) control the user experience/interface (e.g. film strip
75 97
  * visibility) of (other) non-moderator particiapnts.
76 98
  *
77
- * @author Lyubomir Marinov 
99
+ * @author Lyubomir Marinov
78 100
  */
79
-/* public */ class FollowMe {
101
+class FollowMe {
80 102
     /**
81 103
      * Initializes a new {FollowMe} instance.
82 104
      *
@@ -87,15 +109,14 @@ import UIEvents from '../service/UI/UIEvents';
87 109
      * destination (model/state) to receive from the remote moderator if the
88 110
      * local participant is not the moderator
89 111
      */
90
-    /* public */ constructor (conference, UI) {
91
-        /* private */ this._conference = conference;
92
-        /* private */ this._UI = UI;
112
+    constructor (conference, UI) {
113
+        this._conference = conference;
114
+        this._UI = UI;
93 115
 
94 116
         // The states of the local participant which are to be followed (by the
95 117
         // remote participants when the local participant is in her right to
96 118
         // issue such commands).
97
-        /* private */ this._local
98
-            = new State(this._localPropertyChange.bind(this));
119
+        this._local = new State(this._localPropertyChange.bind(this));
99 120
 
100 121
         // Listen to "Follow Me" commands. I'm not sure whether a moderator can
101 122
         // (in lib-jitsi-meet and/or Meet) become a non-moderator. If that's
@@ -104,18 +125,57 @@ import UIEvents from '../service/UI/UIEvents';
104 125
         conference.commands.addCommandListener(
105 126
                 _COMMAND,
106 127
                 this._onFollowMeCommand.bind(this));
107
-        // Listen to (user interface) states of the local participant which are
108
-        // to be followed (by the remote participants). A non-moderator (very
109
-        // likely) can become a moderator so it may be easiest to always track
110
-        // the states of interest.
111
-        UI.addListener(
112
-                UIEvents.TOGGLED_FILM_STRIP,
113
-                this._filmStripToggled.bind(this));
114
-        // TODO Listen to changes in the moderator role of the local
115
-        // participant. When the local participant is granted the moderator
116
-        // role, it may need to start sending "Follow Me" commands. Obviously,
117
-        // this depends on how we decide to enable the feature at runtime as
118
-        // well.
128
+    }
129
+
130
+    /**
131
+     * Adds listeners for the UI states of the local participant which are
132
+     * to be followed (by the remote participants). A non-moderator (very
133
+     * likely) can become a moderator so it may be easiest to always track
134
+     * the states of interest.
135
+     * @private
136
+     */
137
+    _addFollowMeListeners () {
138
+        this.filmStripEventHandler = this._filmStripToggled.bind(this);
139
+        this._UI.addListener(UIEvents.TOGGLED_FILM_STRIP,
140
+                            this.filmStripEventHandler);
141
+
142
+        var self = this;
143
+        this.pinnedEndpointEventHandler = function (smallVideo, isPinned) {
144
+            self._nextOnStage(smallVideo, isPinned);
145
+        };
146
+        this._UI.addListener(UIEvents.PINNED_ENDPOINT,
147
+                            this.pinnedEndpointEventHandler);
148
+
149
+        this.sharedDocEventHandler = this._sharedDocumentToggled.bind(this);
150
+        this._UI.addListener( UIEvents.TOGGLED_SHARED_DOCUMENT,
151
+                            this.sharedDocEventHandler);
152
+    }
153
+
154
+    /**
155
+     * Removes all follow me listeners.
156
+     * @private
157
+     */
158
+    _removeFollowMeListeners () {
159
+        this._UI.removeListener(UIEvents.TOGGLED_FILM_STRIP,
160
+                                this.filmStripEventHandler);
161
+        this._UI.removeListener(UIEvents.TOGGLED_SHARED_DOCUMENT,
162
+                                this.sharedDocEventHandler);
163
+        this._UI.removeListener(UIEvents.PINNED_ENDPOINT,
164
+                                this.pinnedEndpointEventHandler);
165
+    }
166
+
167
+    /**
168
+     * Enables or disabled the follow me functionality
169
+     *
170
+     * @param enable {true} to enable the follow me functionality, {false} -
171
+     * to disable it
172
+     */
173
+    enableFollowMe (enable) {
174
+        this.isEnabled = enable;
175
+        if (this.isEnabled)
176
+            this._addFollowMeListeners();
177
+        else
178
+            this._removeFollowMeListeners();
119 179
     }
120 180
 
121 181
     /**
@@ -125,11 +185,49 @@ import UIEvents from '../service/UI/UIEvents';
125 185
      * @param filmStripVisible {Boolean} {true} if the film strip was shown (as
126 186
      * a result of the toggle) or {false} if the film strip was hidden
127 187
      */
128
-    /* private */ _filmStripToggled (filmStripVisible) {
188
+    _filmStripToggled (filmStripVisible) {
129 189
         this._local.filmStripVisible = filmStripVisible;
130 190
     }
131 191
 
132
-    /* private */ _localPropertyChange (property, oldValue, newValue) {
192
+    /**
193
+     * Notifies this instance that the (visibility of the) shared document was
194
+     * toggled (in the user interface of the local participant).
195
+     *
196
+     * @param sharedDocumentVisible {Boolean} {true} if the shared document was
197
+     * shown (as a result of the toggle) or {false} if it was hidden
198
+     */
199
+    _sharedDocumentToggled (sharedDocumentVisible) {
200
+        this._local.sharedDocumentVisible = sharedDocumentVisible;
201
+    }
202
+
203
+    /**
204
+     * Changes the nextOnPage property value.
205
+     *
206
+     * @param smallVideo the {SmallVideo} that was pinned or unpinned
207
+     * @param isPinned indicates if the given {SmallVideo} was pinned or
208
+     * unpinned
209
+     * @private
210
+     */
211
+    _nextOnStage (smallVideo, isPinned) {
212
+        if (!this._conference.isModerator)
213
+            return;
214
+
215
+        var nextOnStage = null;
216
+        if(isPinned)
217
+            nextOnStage = smallVideo.getId();
218
+
219
+        this._local.nextOnStage = nextOnStage;
220
+    }
221
+
222
+    /**
223
+     * Sends the follow-me command, when a local property change occurs.
224
+     *
225
+     * @param property the property name
226
+     * @param oldValue the old value
227
+     * @param newValue the new value
228
+     * @private
229
+     */
230
+    _localPropertyChange (property, oldValue, newValue) {
133 231
         // Only a moderator is allowed to send commands.
134 232
         var conference = this._conference;
135 233
         if (!conference.isModerator)
@@ -141,12 +239,14 @@ import UIEvents from '../service/UI/UIEvents';
141 239
         // sendCommand!
142 240
         commands.removeCommand(_COMMAND);
143 241
         var self = this;
144
-        commands.sendCommand(
242
+        commands.sendCommandOnce(
145 243
                 _COMMAND,
146 244
                 {
147 245
                     attributes: {
148 246
                         filmStripVisible: self._local.filmStripVisible,
149
-                    },
247
+                        nextOnStage: self._local.nextOnStage,
248
+                        sharedDocumentVisible: self._local.sharedDocumentVisible
249
+                    }
150 250
                 });
151 251
     }
152 252
 
@@ -159,7 +259,7 @@ import UIEvents from '../service/UI/UIEvents';
159 259
      * notable idiosyncrasy of the Command(s) API to be mindful of here is that
160 260
      * the command may be issued by the local participant.
161 261
      */
162
-    /* private */ _onFollowMeCommand ({ attributes }, id) {
262
+    _onFollowMeCommand ({ attributes }, id) {
163 263
         // We require to know who issued the command because (1) only a
164 264
         // moderator is allowed to send commands and (2) a command MUST be
165 265
         // issued by a defined commander.
@@ -169,28 +269,60 @@ import UIEvents from '../service/UI/UIEvents';
169 269
         // to act upon them.
170 270
         if (this._conference.isLocalId(id))
171 271
             return;
272
+
172 273
         // TODO Don't obey commands issued by non-moderators.
173 274
 
174
-        // Apply the received/remote command to the user experience/interface
275
+        // Applies the received/remote command to the user experience/interface
175 276
         // of the local participant.
277
+        this._onFilmStripVisible(attributes.filmStripVisible);
278
+        this._onNextOnStage(attributes.nextOnStage);
279
+        this._onSharedDocumentVisible(attributes.sharedDocumentVisible);
280
+    }
176 281
 
177
-        // filmStripVisible
178
-        var filmStripVisible = attributes.filmStripVisible;
282
+    _onFilmStripVisible(filmStripVisible) {
179 283
         if (typeof filmStripVisible !== 'undefined') {
180 284
             // XXX The Command(s) API doesn't preserve the types (of
181 285
             // attributes, at least) at the time of this writing so take into
182 286
             // account that what originated as a Boolean may be a String on
183 287
             // receipt.
184 288
             filmStripVisible = (filmStripVisible == 'true');
289
+
185 290
             // FIXME The UI (module) very likely doesn't (want to) expose its
186 291
             // eventEmitter as a public field. I'm not sure at the time of this
187 292
             // writing whether calling UI.toggleFilmStrip() is acceptable (from
188 293
             // a design standpoint) either.
189
-            this._UI.eventEmitter.emit(
294
+            if (filmStripVisible !== FilmStrip.isFilmStripVisible())
295
+                this._UI.eventEmitter.emit(
190 296
                     UIEvents.TOGGLE_FILM_STRIP,
191 297
                     filmStripVisible);
192 298
         }
193 299
     }
300
+
301
+    _onNextOnStage(id) {
302
+
303
+        var clickId = null;
304
+        if(typeof id !== 'undefined' && !VideoLayout.isPinned(id))
305
+            clickId = id;
306
+        else if (typeof id == 'undefined')
307
+            clickId = VideoLayout.getPinnedId();
308
+
309
+        if (clickId !== null)
310
+            VideoLayout.handleVideoThumbClicked(clickId);
311
+    }
312
+
313
+    _onSharedDocumentVisible(sharedDocumentVisible) {
314
+        if (typeof sharedDocumentVisible !== 'undefined') {
315
+            // XXX The Command(s) API doesn't preserve the types (of
316
+            // attributes, at least) at the time of this writing so take into
317
+            // account that what originated as a Boolean may be a String on
318
+            // receipt.
319
+            sharedDocumentVisible = (sharedDocumentVisible == 'true');
320
+
321
+            if (sharedDocumentVisible
322
+                !== this._UI.getSharedDocumentManager().isVisible())
323
+                this._UI.getSharedDocumentManager().toggleEtherpad();
324
+        }
325
+    }
194 326
 }
195 327
 
196 328
 export default FollowMe;

+ 35
- 2
modules/UI/UI.js View File

@@ -35,6 +35,8 @@ UI.eventEmitter = eventEmitter;
35 35
 let etherpadManager;
36 36
 let sharedVideoManager;
37 37
 
38
+let followMeHandler;
39
+
38 40
 /**
39 41
  * Prompt user for nickname.
40 42
  */
@@ -252,7 +254,7 @@ UI.initConference = function () {
252 254
     // other participants' UI. Consequently, it needs (1) read and write access
253 255
     // to the UI (depending on the moderator role of the local participant) and
254 256
     // (2) APP.conference as means of communication between the participants.
255
-    new FollowMe(APP.conference, UI);
257
+    followMeHandler = new FollowMe(APP.conference, UI);
256 258
 };
257 259
 
258 260
 UI.mucJoined = function () {
@@ -290,6 +292,11 @@ function registerListeners() {
290 292
         UI.toggleFilmStrip();
291 293
         VideoLayout.resizeVideoArea(PanelToggler.isVisible(), true, false);
292 294
     });
295
+
296
+    UI.addListener(UIEvents.FOLLOW_ME_ENABLED, function (isEnabled) {
297
+        if (followMeHandler)
298
+            followMeHandler.enableFollowMe(isEnabled);
299
+    });
293 300
 }
294 301
 
295 302
 /**
@@ -478,10 +485,19 @@ UI.initEtherpad = function (name) {
478 485
         return;
479 486
     }
480 487
     console.log('Etherpad is enabled');
481
-    etherpadManager = new EtherpadManager(config.etherpad_base, name);
488
+    etherpadManager
489
+        = new EtherpadManager(config.etherpad_base, name, eventEmitter);
482 490
     Toolbar.showEtherpadButton();
483 491
 };
484 492
 
493
+/**
494
+ * Returns the shared document manager object.
495
+ * @return {EtherpadManager} the shared document manager object
496
+ */
497
+UI.getSharedDocumentManager = function () {
498
+    return etherpadManager;
499
+};
500
+
485 501
 /**
486 502
  * Show user on UI.
487 503
  * @param {string} id user id
@@ -549,6 +565,7 @@ UI.updateLocalRole = function (isModerator) {
549 565
     Toolbar.showRecordingButton(isModerator);
550 566
     Toolbar.showSharedVideoButton(isModerator);
551 567
     SettingsMenu.showStartMutedOptions(isModerator);
568
+    SettingsMenu.showFollowMeOptions(isModerator);
552 569
 
553 570
     if (isModerator) {
554 571
         messageHandler.notify(null, "notify.me", 'connected', "notify.moderator");
@@ -686,10 +703,26 @@ UI.setVideoMuted = function (id, muted) {
686 703
     }
687 704
 };
688 705
 
706
+/**
707
+ * Adds a listener that would be notified on the given type of event.
708
+ *
709
+ * @param type the type of the event we're listening for
710
+ * @param listener a function that would be called when notified
711
+ */
689 712
 UI.addListener = function (type, listener) {
690 713
     eventEmitter.on(type, listener);
691 714
 };
692 715
 
716
+/**
717
+ * Removes the given listener for the given type of event.
718
+ *
719
+ * @param type the type of the event we're listening for
720
+ * @param listener the listener we want to remove
721
+ */
722
+UI.removeListener = function (type, listener) {
723
+    eventEmitter.removeListener(type, listener);
724
+};
725
+
693 726
 UI.clickOnVideo = function (videoNumber) {
694 727
     var remoteVideos = $(".videocontainer:not(#mixedstream)");
695 728
     if (remoteVideos.length > videoNumber) {

+ 12
- 4
modules/UI/etherpad/Etherpad.js View File

@@ -3,6 +3,7 @@
3 3
 import VideoLayout from "../videolayout/VideoLayout";
4 4
 import LargeContainer from '../videolayout/LargeContainer';
5 5
 import UIUtil from "../util/UIUtil";
6
+import UIEvents from "../../../service/UI/UIEvents";
6 7
 import SidePanelToggler from "../side_pannels/SidePanelToggler";
7 8
 import FilmStrip from '../videolayout/FilmStrip';
8 9
 
@@ -58,6 +59,7 @@ const ETHERPAD_CONTAINER_TYPE = "etherpad";
58 59
  * Container for Etherpad iframe.
59 60
  */
60 61
 class Etherpad extends LargeContainer {
62
+
61 63
     constructor (domain, name) {
62 64
         super();
63 65
 
@@ -149,13 +151,14 @@ class Etherpad extends LargeContainer {
149 151
  * Manager of the Etherpad frame.
150 152
  */
151 153
 export default class EtherpadManager {
152
-    constructor (domain, name) {
154
+    constructor (domain, name, eventEmitter) {
153 155
         if (!domain || !name) {
154 156
             throw new Error("missing domain or name");
155 157
         }
156 158
 
157 159
         this.domain = domain;
158 160
         this.name = name;
161
+        this.eventEmitter = eventEmitter;
159 162
         this.etherpad = null;
160 163
     }
161 164
 
@@ -163,6 +166,10 @@ export default class EtherpadManager {
163 166
         return !!this.etherpad;
164 167
     }
165 168
 
169
+    isVisible() {
170
+        return VideoLayout.isLargeContainerTypeVisible(ETHERPAD_CONTAINER_TYPE);
171
+    }
172
+
166 173
     /**
167 174
      * Create new Etherpad frame.
168 175
      */
@@ -183,11 +190,12 @@ export default class EtherpadManager {
183 190
             this.openEtherpad();
184 191
         }
185 192
 
186
-        let isVisible = VideoLayout.isLargeContainerTypeVisible(
187
-            ETHERPAD_CONTAINER_TYPE
188
-        );
193
+        let isVisible = this.isVisible();
189 194
 
190 195
         VideoLayout.showLargeVideoContainer(
191 196
             ETHERPAD_CONTAINER_TYPE, !isVisible);
197
+
198
+        this.eventEmitter
199
+            .emit(UIEvents.TOGGLED_SHARED_DOCUMENT, !isVisible);
192 200
     }
193 201
 }

+ 2
- 2
modules/UI/shared_video/SharedVideo.js View File

@@ -120,7 +120,7 @@ export default class SharedVideoManager {
120 120
 
121 121
             VideoLayout.addLargeVideoContainer(
122 122
                 SHARED_VIDEO_CONTAINER_TYPE, self.sharedVideo);
123
-            VideoLayout.handleVideoThumbClicked(true, self.url);
123
+            VideoLayout.handleVideoThumbClicked(self.url);
124 124
 
125 125
             self.isSharedVideoShown = true;
126 126
 
@@ -372,7 +372,7 @@ SharedVideoThumb.prototype.createContainer = function (spanId) {
372 372
  * The thumb click handler.
373 373
  */
374 374
 SharedVideoThumb.prototype.videoClick = function () {
375
-    VideoLayout.handleVideoThumbClicked(true, this.url);
375
+    VideoLayout.handleVideoThumbClicked(this.url);
376 376
 };
377 377
 
378 378
 /**

+ 21
- 0
modules/UI/side_pannels/settings/SettingsMenu.js View File

@@ -89,6 +89,14 @@ export default {
89 89
             );
90 90
         });
91 91
 
92
+        // FOLLOW ME
93
+        $("#followMeOptions").change(function () {
94
+            let isFollowMeEnabled = $("#followMeCheckBox").is(":checked");
95
+            emitter.emit(
96
+                UIEvents.FOLLOW_ME_ENABLED,
97
+                isFollowMeEnabled
98
+            );
99
+        });
92 100
 
93 101
         // LANGUAGES BOX
94 102
         let languagesBox = $("#languages_selectbox");
@@ -135,6 +143,19 @@ export default {
135 143
         $("#startVideoMuted").attr("checked", startVideoMuted);
136 144
     },
137 145
 
146
+    /**
147
+     * Shows/hides the follow me options in the settings dialog.
148
+     *
149
+     * @param {boolean} show {true} to show those options, {false} to hide them
150
+     */
151
+    showFollowMeOptions (show) {
152
+        if (show) {
153
+            $("#followMeOptions").css("display", "block");
154
+        } else {
155
+            $("#followMeOptions").css("display", "none");
156
+        }
157
+    },
158
+
138 159
     /**
139 160
      * Check if settings menu is visible or not.
140 161
      * @returns {boolean}

+ 1
- 1
modules/UI/videolayout/LocalVideo.js View File

@@ -158,7 +158,7 @@ LocalVideo.prototype.changeVideo = function (stream) {
158 158
         if (event.stopPropagation) {
159 159
             event.stopPropagation();
160 160
         }
161
-        this.VideoLayout.handleVideoThumbClicked(true, this.id);
161
+        this.VideoLayout.handleVideoThumbClicked(this.id);
162 162
     };
163 163
 
164 164
     let localVideoContainerSelector = $('#localVideoContainer');

+ 1
- 1
modules/UI/videolayout/RemoteVideo.js View File

@@ -220,7 +220,7 @@ RemoteVideo.prototype.addRemoteStreamElement = function (stream) {
220 220
 
221 221
         // ignore click if it was done in popup menu
222 222
         if ($(source).parents('.popupmenu').length === 0) {
223
-            this.VideoLayout.handleVideoThumbClicked(false, this.id);
223
+            this.VideoLayout.handleVideoThumbClicked(this.id);
224 224
         }
225 225
 
226 226
         // On IE we need to populate this handler on video <object>

+ 8
- 0
modules/UI/videolayout/SmallVideo.js View File

@@ -20,6 +20,14 @@ function setVisibility(selector, show) {
20 20
     }
21 21
 }
22 22
 
23
+/**
24
+ * Returns the identifier of this small video.
25
+ *
26
+ * @returns the identifier of this small video
27
+ */
28
+SmallVideo.prototype.getId = function () {
29
+    return this.id;
30
+};
23 31
 
24 32
 /* Indicates if this small video is currently visible.
25 33
  *

+ 37
- 31
modules/UI/videolayout/VideoLayout.js View File

@@ -31,7 +31,7 @@ var eventEmitter = null;
31 31
  * Currently focused video jid
32 32
  * @type {String}
33 33
  */
34
-var focusedVideoResourceJid = null;
34
+var pinnedId = null;
35 35
 
36 36
 /**
37 37
  * On contact list item clicked.
@@ -49,7 +49,7 @@ function onContactClicked (id) {
49 49
         if (remoteVideo.hasVideoStarted()) {
50 50
             // We have a video src, great! Let's update the large video
51 51
             // now.
52
-            VideoLayout.handleVideoThumbClicked(false, id);
52
+            VideoLayout.handleVideoThumbClicked(id);
53 53
         } else {
54 54
 
55 55
             // If we don't have a video src for jid, there's absolutely
@@ -63,7 +63,7 @@ function onContactClicked (id) {
63 63
             // picked up later by the lastN changed event handler.
64 64
 
65 65
             lastNPickupId = id;
66
-            eventEmitter.emit(UIEvents.PINNED_ENDPOINT, id);
66
+            eventEmitter.emit(UIEvents.PINNED_ENDPOINT, remoteVideo, true);
67 67
         }
68 68
     }
69 69
 }
@@ -209,7 +209,7 @@ var VideoLayout = {
209 209
 
210 210
         // We'll show user's avatar if he is the dominant speaker or if
211 211
         // his video thumbnail is pinned
212
-        if (remoteVideos[id] && (id === focusedVideoResourceJid
212
+        if (remoteVideos[id] && (id === pinnedId
213 213
                                 || id === currentDominantSpeaker)) {
214 214
             newId = id;
215 215
         } else {
@@ -289,20 +289,33 @@ var VideoLayout = {
289 289
         return smallVideo ? smallVideo.getVideoType() : null;
290 290
     },
291 291
 
292
-    handleVideoThumbClicked (noPinnedEndpointChangedEvent,
293
-                                          resourceJid) {
294
-        if(focusedVideoResourceJid) {
292
+    isPinned (id) {
293
+        return (pinnedId) ? (id === pinnedId) : false;
294
+    },
295
+
296
+    getPinnedId () {
297
+        return pinnedId;
298
+    },
299
+
300
+    /**
301
+     * Handles the click on a video thumbnail.
302
+     *
303
+     * @param id the identifier of the video thumbnail
304
+     */
305
+    handleVideoThumbClicked (id) {
306
+        if(pinnedId) {
295 307
             var oldSmallVideo
296
-                    = VideoLayout.getSmallVideo(focusedVideoResourceJid);
308
+                    = VideoLayout.getSmallVideo(pinnedId);
297 309
             if (oldSmallVideo && !interfaceConfig.filmStripOnly)
298 310
                 oldSmallVideo.focus(false);
299 311
         }
300 312
 
301
-        var smallVideo = VideoLayout.getSmallVideo(resourceJid);
302
-        // Unlock current focused.
303
-        if (focusedVideoResourceJid === resourceJid)
313
+        var smallVideo = VideoLayout.getSmallVideo(id);
314
+
315
+        // Unpin if currently pinned.
316
+        if (pinnedId === id)
304 317
         {
305
-            focusedVideoResourceJid = null;
318
+            pinnedId = null;
306 319
             // Enable the currently set dominant speaker.
307 320
             if (currentDominantSpeaker) {
308 321
                 if(smallVideo && smallVideo.hasVideo()) {
@@ -310,26 +323,23 @@ var VideoLayout = {
310 323
                 }
311 324
             }
312 325
 
313
-            if (!noPinnedEndpointChangedEvent) {
314
-                eventEmitter.emit(UIEvents.PINNED_ENDPOINT);
315
-            }
326
+            eventEmitter.emit(UIEvents.PINNED_ENDPOINT, smallVideo, false);
327
+
316 328
             return;
317 329
         }
318 330
 
319 331
         // Lock new video
320
-        focusedVideoResourceJid = resourceJid;
332
+        pinnedId = id;
321 333
 
322 334
         // Update focused/pinned interface.
323
-        if (resourceJid) {
335
+        if (id) {
324 336
             if (smallVideo && !interfaceConfig.filmStripOnly)
325 337
                 smallVideo.focus(true);
326 338
 
327
-            if (!noPinnedEndpointChangedEvent) {
328
-                eventEmitter.emit(UIEvents.PINNED_ENDPOINT, resourceJid);
329
-            }
339
+            eventEmitter.emit(UIEvents.PINNED_ENDPOINT, smallVideo, true);
330 340
         }
331 341
 
332
-        this.updateLargeVideo(resourceJid);
342
+        this.updateLargeVideo(id);
333 343
     },
334 344
 
335 345
     /**
@@ -372,10 +382,10 @@ var VideoLayout = {
372 382
         // Update the large video to the last added video only if there's no
373 383
         // current dominant, focused speaker or update it to
374 384
         // the current dominant speaker.
375
-        if ((!focusedVideoResourceJid &&
385
+        if ((!pinnedId &&
376 386
             !currentDominantSpeaker &&
377 387
             this.isLargeContainerTypeVisible(VIDEO_CONTAINER_TYPE)) ||
378
-            focusedVideoResourceJid === resourceJid ||
388
+            pinnedId === resourceJid ||
379 389
             (resourceJid &&
380 390
                 currentDominantSpeaker === resourceJid)) {
381 391
             this.updateLargeVideo(resourceJid, true);
@@ -531,7 +541,7 @@ var VideoLayout = {
531 541
         // since we don't want to switch to local video.
532 542
         // Update the large video if the video source is already available,
533 543
         // otherwise wait for the "videoactive.jingle" event.
534
-        if (!focusedVideoResourceJid
544
+        if (!pinnedId
535 545
             && remoteVideo.hasVideoStarted()
536 546
             && !this.getCurrentlyOnLargeContainer().stayOnStage()) {
537 547
             this.updateLargeVideo(id);
@@ -650,11 +660,7 @@ var VideoLayout = {
650 660
                         // Clean up the lastN pickup id.
651 661
                         lastNPickupId = null;
652 662
 
653
-                        // Don't fire the events again, they've already
654
-                        // been fired in the contact list click handler.
655
-                        VideoLayout.handleVideoThumbClicked(
656
-                            false,
657
-                            resourceJid);
663
+                        VideoLayout.handleVideoThumbClicked(resourceJid);
658 664
 
659 665
                         updateLargeVideo = false;
660 666
                     }
@@ -741,9 +747,9 @@ var VideoLayout = {
741 747
 
742 748
     removeParticipantContainer (id) {
743 749
         // Unlock large video
744
-        if (focusedVideoResourceJid === id) {
750
+        if (pinnedId === id) {
745 751
             console.info("Focused video owner has left the conference");
746
-            focusedVideoResourceJid = null;
752
+            pinnedId = null;
747 753
         }
748 754
 
749 755
         if (currentDominantSpeaker === id) {

+ 9
- 3
service/UI/UIEvents.js View File

@@ -56,14 +56,20 @@ export default {
56 56
      *
57 57
      * @see {TOGGLE_FILM_STRIP}
58 58
      */
59
-    TOGGLED_FILM_STRIP: "UI.toggled_fim_strip",
59
+    TOGGLED_FILM_STRIP: "UI.toggled_film_strip",
60 60
     TOGGLE_SCREENSHARING: "UI.toggle_screensharing",
61
+    TOGGLED_SHARED_DOCUMENT: "UI.toggled_shared_document",
61 62
     CONTACT_CLICKED: "UI.contact_clicked",
62 63
     HANGUP: "UI.hangup",
63 64
     LOGOUT: "UI.logout",
64 65
     RECORDING_TOGGLE: "UI.recording_toggle",
65 66
     SIP_DIAL: "UI.sip_dial",
66
-    SUBEJCT_CHANGED: "UI.subject_changed",
67
+    SUBJECT_CHANGED: "UI.subject_changed",
67 68
     VIDEO_DEVICE_CHANGED: "UI.video_device_changed",
68
-    AUDIO_DEVICE_CHANGED: "UI.audio_device_changed"
69
+    AUDIO_DEVICE_CHANGED: "UI.audio_device_changed",
70
+    /**
71
+     * Notifies interested listeners that the follow-me feature is enabled or
72
+     * disabled.
73
+     */
74
+    FOLLOW_ME_ENABLED: "UI.follow_me_enabled"
69 75
 };

Loading…
Cancel
Save