浏览代码

Implements an initial (demo) version of "Follow Me" for film strip visibility.

j8
Lyubomir Marinov 9 年前
父节点
当前提交
605a892f78
共有 4 个文件被更改,包括 229 次插入2 次删除
  1. 196
    0
      modules/FollowMe.js
  2. 9
    1
      modules/UI/UI.js
  3. 16
    1
      modules/UI/videolayout/FilmStrip.js
  4. 8
    0
      service/UI/UIEvents.js

+ 196
- 0
modules/FollowMe.js 查看文件

@@ -0,0 +1,196 @@
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
+ */
16
+
17
+import UIEvents from '../service/UI/UIEvents';
18
+
19
+/**
20
+ * The (name of the) command which transports the state (represented by
21
+ * {State} for the local state at the time of this writing) of a {FollowMe}
22
+ * (instance) between participants.
23
+ */
24
+/* private */ const _COMMAND = "follow-me";
25
+
26
+/**
27
+ * Represents the set of {FollowMe}-related states (properties and their
28
+ * respective values) which are to be followed by a participant. {FollowMe}
29
+ * will send {_COMMAND} whenever a property of {State} changes (if the local
30
+ * participant is in her right to issue such a command, of course).
31
+ */
32
+/* private */ class State {
33
+    /**
34
+     * Initializes a new {State} instance.
35
+     *
36
+     * @param propertyChangeCallback {Function} which is to be called when a
37
+     * property of the new instance has its value changed from an old value
38
+     * into a (different) new value. The function is supplied with the name of
39
+     * the property, the old value of the property before the change, and the
40
+     * new value of the property after the change.
41
+     */
42
+    /* public */ constructor (propertyChangeCallback) {
43
+        /* private*/ this._propertyChangeCallback = propertyChangeCallback;
44
+    }
45
+
46
+    /* public */ get filmStripVisible () { return this._filmStripVisible; }
47
+
48
+    /* public */ set filmStripVisible (b) {
49
+        var oldValue = this._filmStripVisible;
50
+        if (oldValue !== b) {
51
+            this._filmStripVisible = b;
52
+            this._firePropertyChange('filmStripVisible', oldValue, b);
53
+        }
54
+    }
55
+
56
+    /**
57
+     * Invokes {_propertyChangeCallback} to notify it that {property} had its
58
+     * value changed from {oldValue} to {newValue}.
59
+     *
60
+     * @param property the name of the property which had its value changed
61
+     * from {oldValue} to {newValue}
62
+     * @param oldValue the value of {property} before the change
63
+     * @param newValue the value of {property} after the change
64
+     */
65
+    /* private */ _firePropertyChange (property, oldValue, newValue) {
66
+        var propertyChangeCallback = this._propertyChangeCallback;
67
+        if (propertyChangeCallback)
68
+            propertyChangeCallback(property, oldValue, newValue);
69
+    }
70
+}
71
+
72
+/**
73
+ * Represents the "Follow Me" feature which enables a moderator to
74
+ * (partially) control the user experience/interface (e.g. film strip
75
+ * visibility) of (other) non-moderator particiapnts.
76
+ *
77
+ * @author Lyubomir Marinov 
78
+ */
79
+/* public */ class FollowMe {
80
+    /**
81
+     * Initializes a new {FollowMe} instance.
82
+     *
83
+     * @param conference the {conference} which is to transport
84
+     * {FollowMe}-related information between participants
85
+     * @param UI the {UI} which is the source (model/state) to be sent to
86
+     * remote participants if the local participant is the moderator or the
87
+     * destination (model/state) to receive from the remote moderator if the
88
+     * local participant is not the moderator
89
+     */
90
+    /* public */ constructor (conference, UI) {
91
+        /* private */ this._conference = conference;
92
+        /* private */ this._UI = UI;
93
+
94
+        // The states of the local participant which are to be followed (by the
95
+        // remote participants when the local participant is in her right to
96
+        // issue such commands).
97
+        /* private */ this._local
98
+            = new State(this._localPropertyChange.bind(this));
99
+
100
+        // Listen to "Follow Me" commands. I'm not sure whether a moderator can
101
+        // (in lib-jitsi-meet and/or Meet) become a non-moderator. If that's
102
+        // possible, then it may be easiest to always listen to commands. The
103
+        // listener will validate received commands before acting on them.
104
+        conference.commands.addCommandListener(
105
+                _COMMAND,
106
+                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.
119
+    }
120
+
121
+    /**
122
+     * Notifies this instance that the (visibility of the) film strip was
123
+     * toggled (in the user interface of the local participant).
124
+     *
125
+     * @param filmStripVisible {Boolean} {true} if the film strip was shown (as
126
+     * a result of the toggle) or {false} if the film strip was hidden
127
+     */
128
+    /* private */ _filmStripToggled (filmStripVisible) {
129
+        this._local.filmStripVisible = filmStripVisible;
130
+    }
131
+
132
+    /* private */ _localPropertyChange (property, oldValue, newValue) {
133
+        // Only a moderator is allowed to send commands.
134
+        var conference = this._conference;
135
+        if (!conference.isModerator)
136
+            return;
137
+
138
+        var commands = conference.commands;
139
+        // XXX The "Follow Me" command represents a snapshot of all states
140
+        // which are to be followed so don't forget to removeCommand before
141
+        // sendCommand!
142
+        commands.removeCommand(_COMMAND);
143
+        var self = this;
144
+        commands.sendCommand(
145
+                _COMMAND,
146
+                {
147
+                    attributes: {
148
+                        filmStripVisible: self._local.filmStripVisible,
149
+                    },
150
+                });
151
+    }
152
+
153
+    /**
154
+     * Notifies this instance about a &qout;Follow Me&qout; command (delivered
155
+     * by the Command(s) API of {this._conference}).
156
+     *
157
+     * @param attributes the attributes {Object} carried by the command
158
+     * @param id the identifier of the participant who issued the command. A
159
+     * notable idiosyncrasy of the Command(s) API to be mindful of here is that
160
+     * the command may be issued by the local participant.
161
+     */
162
+    /* private */ _onFollowMeCommand ({ attributes }, id) {
163
+        // We require to know who issued the command because (1) only a
164
+        // moderator is allowed to send commands and (2) a command MUST be
165
+        // issued by a defined commander.
166
+        if (typeof id === 'undefined')
167
+            return;
168
+        // The Command(s) API will send us our own commands and we don't want
169
+        // to act upon them.
170
+        if (this._conference.isLocalId(id))
171
+            return;
172
+        // TODO Don't obey commands issued by non-moderators.
173
+
174
+        // Apply the received/remote command to the user experience/interface
175
+        // of the local participant.
176
+
177
+        // filmStripVisible
178
+        var filmStripVisible = attributes.filmStripVisible;
179
+        if (typeof filmStripVisible !== 'undefined') {
180
+            // XXX The Command(s) API doesn't preserve the types (of
181
+            // attributes, at least) at the time of this writing so take into
182
+            // account that what originated as a Boolean may be a String on
183
+            // receipt.
184
+            filmStripVisible = (filmStripVisible == 'true');
185
+            // FIXME The UI (module) very likely doesn't (want to) expose its
186
+            // eventEmitter as a public field. I'm not sure at the time of this
187
+            // writing whether calling UI.toggleFilmStrip() is acceptable (from
188
+            // a design standpoint) either.
189
+            this._UI.eventEmitter.emit(
190
+                    UIEvents.TOGGLE_FILM_STRIP,
191
+                    filmStripVisible);
192
+        }
193
+    }
194
+}
195
+
196
+export default FollowMe;

+ 9
- 1
modules/UI/UI.js 查看文件

@@ -27,6 +27,8 @@ var messageHandler = UI.messageHandler;
27 27
 var JitsiPopover = require("./util/JitsiPopover");
28 28
 var Feedback = require("./Feedback");
29 29
 
30
+import FollowMe from "../FollowMe";
31
+
30 32
 var eventEmitter = new EventEmitter();
31 33
 UI.eventEmitter = eventEmitter;
32 34
 
@@ -246,6 +248,12 @@ UI.initConference = function () {
246 248
     if(!interfaceConfig.filmStripOnly) {
247 249
         Feedback.init();
248 250
     }
251
+
252
+    // FollowMe attempts to copy certain aspects of the moderator's UI into the
253
+    // other participants' UI. Consequently, it needs (1) read and write access
254
+    // to the UI (depending on the moderator role of the local participant) and
255
+    // (2) APP.conference as means of communication between the participants.
256
+    new FollowMe(APP.conference, UI);
249 257
 };
250 258
 
251 259
 UI.mucJoined = function () {
@@ -326,7 +334,7 @@ UI.start = function () {
326 334
     registerListeners();
327 335
 
328 336
     BottomToolbar.init();
329
-    FilmStrip.init();
337
+    FilmStrip.init(eventEmitter);
330 338
 
331 339
     VideoLayout.init(eventEmitter);
332 340
     if (!interfaceConfig.filmStripOnly) {

+ 16
- 1
modules/UI/videolayout/FilmStrip.js 查看文件

@@ -1,12 +1,19 @@
1 1
 /* global $, APP, interfaceConfig, config*/
2 2
 
3
+import UIEvents from "../../../service/UI/UIEvents";
3 4
 import UIUtil from "../util/UIUtil";
4 5
 
5 6
 const thumbAspectRatio = 16.0 / 9.0;
6 7
 
7 8
 const FilmStrip = {
8
-    init () {
9
+    /**
10
+     *
11
+     * @param eventEmitter the {EventEmitter} through which {FilmStrip} is to
12
+     * emit/fire {UIEvents} (such as {UIEvents.TOGGLED_FILM_STRIP}).
13
+     */
14
+    init (eventEmitter) {
9 15
         this.filmStrip = $('#remoteVideos');
16
+        this.eventEmitter = eventEmitter;
10 17
     },
11 18
 
12 19
     /**
@@ -24,6 +31,14 @@ const FilmStrip = {
24 31
         }
25 32
 
26 33
         this.filmStrip.toggleClass("hidden");
34
+
35
+        // Emit/fire UIEvents.TOGGLED_FILM_STRIP.
36
+        var eventEmitter = this.eventEmitter;
37
+        if (eventEmitter) {
38
+            eventEmitter.emit(
39
+                    UIEvents.TOGGLED_FILM_STRIP,
40
+                    this.isFilmStripVisible());
41
+        }
27 42
     },
28 43
 
29 44
     isFilmStripVisible () {

+ 8
- 0
service/UI/UIEvents.js 查看文件

@@ -46,6 +46,14 @@ export default {
46 46
      * @see {TOGGLED_FILM_STRIP}
47 47
      */
48 48
     TOGGLE_FILM_STRIP: "UI.toggle_film_strip",
49
+    /**
50
+     * Notifies that the film strip was (actually) toggled. The event supplies
51
+     * a {Boolean} (primitive) value indicating the visibility of the film
52
+     * strip after the toggling (at the time of the event emission).
53
+     *
54
+     * @see {TOGGLE_FILM_STRIP}
55
+     */
56
+    TOGGLED_FILM_STRIP: "UI.toggled_fim_strip",
49 57
     TOGGLE_SCREENSHARING: "UI.toggle_screensharing",
50 58
     CONTACT_CLICKED: "UI.contact_clicked",
51 59
     HANGUP: "UI.hangup",

正在加载...
取消
保存