Просмотр исходного кода

Merge remote-tracking branch 'remotes/upstream/master' into gum_permission_dialog_guidance

j8
tsareg 9 лет назад
Родитель
Сommit
f03b228eea

+ 1
- 3
.gitignore Просмотреть файл

@@ -4,8 +4,6 @@ node_modules
4 4
 *.iml
5 5
 .*.tmp
6 6
 deploy-local.sh
7
-libs/app.bundle.*
8
-libs/lib-jitsi-meet*
9
-libs/external_connect.js
7
+libs/
10 8
 all.css
11 9
 .remote-sync.json

+ 11
- 2
Makefile Просмотреть файл

@@ -8,8 +8,9 @@ DEPLOY_DIR = libs
8 8
 BROWSERIFY_FLAGS = -d
9 9
 OUTPUT_DIR = .
10 10
 LIBJITSIMEET_DIR = node_modules/lib-jitsi-meet/
11
+IFRAME_API_DIR = ./modules/API/external
11 12
 
12
-all: update-deps compile uglify deploy clean
13
+all: update-deps compile compile-iframe-api uglify uglify-iframe-api deploy clean
13 14
 
14 15
 update-deps:
15 16
 	$(NPM) install
@@ -17,8 +18,11 @@ update-deps:
17 18
 compile:
18 19
 	$(BROWSERIFY) $(BROWSERIFY_FLAGS) -e app.js -s APP | $(EXORCIST) $(OUTPUT_DIR)/app.bundle.js.map > $(OUTPUT_DIR)/app.bundle.js
19 20
 
21
+compile-iframe-api:
22
+	$(BROWSERIFY) $(BROWSERIFY_FLAGS) -e $(IFRAME_API_DIR)/external_api.js -s JitsiMeetExternalAPI | $(EXORCIST) $(OUTPUT_DIR)/external_api.js.map > $(OUTPUT_DIR)/external_api.js
23
+
20 24
 clean:
21
-	rm -f $(OUTPUT_DIR)/app.bundle.*
25
+	rm -f $(OUTPUT_DIR)/app.bundle.* $(OUTPUT_DIR)/external_api.*
22 26
 
23 27
 deploy: deploy-init deploy-appbundle deploy-lib-jitsi-meet deploy-css deploy-local
24 28
 
@@ -28,6 +32,8 @@ deploy-init:
28 32
 deploy-appbundle:
29 33
 	cp $(OUTPUT_DIR)/app.bundle.min.js $(OUTPUT_DIR)/app.bundle.min.map \
30 34
 	$(OUTPUT_DIR)/app.bundle.js $(OUTPUT_DIR)/app.bundle.js.map \
35
+	$(OUTPUT_DIR)/external_api.js.map $(OUTPUT_DIR)/external_api.js \
36
+	$(OUTPUT_DIR)/external_api.min.map $(OUTPUT_DIR)/external_api.min.js \
31 37
 	$(DEPLOY_DIR)
32 38
 
33 39
 deploy-lib-jitsi-meet:
@@ -46,6 +52,9 @@ deploy-local:
46 52
 uglify:
47 53
 	$(UGLIFYJS) -p relative $(OUTPUT_DIR)/app.bundle.js -o $(OUTPUT_DIR)/app.bundle.min.js --source-map $(OUTPUT_DIR)/app.bundle.min.map --in-source-map $(OUTPUT_DIR)/app.bundle.js.map
48 54
 
55
+uglify-iframe-api:
56
+	$(UGLIFYJS) -p relative $(OUTPUT_DIR)/external_api.js -o $(OUTPUT_DIR)/external_api.min.js --source-map $(OUTPUT_DIR)/external_api.min.map --in-source-map $(OUTPUT_DIR)/external_api.js.map
57
+
49 58
 
50 59
 source-package:
51 60
 	mkdir -p source_package/jitsi-meet/css && \

+ 1
- 4
app.js Просмотреть файл

@@ -116,10 +116,7 @@ function init() {
116 116
             APP.keyboardshortcut.init();
117 117
         }).catch(function (err) {
118 118
             APP.UI.hideRingOverLay();
119
-            APP.API.sendPostisMessage({
120
-                method: 'video-conference-left',
121
-                params: {roomName: APP.conference.roomName}
122
-            });
119
+            APP.API.notifyConferenceLeft(APP.conference.roomName);
123 120
             console.error(err);
124 121
         });
125 122
     }

+ 52
- 11
conference.js Просмотреть файл

@@ -207,10 +207,7 @@ function maybeRedirectToWelcomePage() {
207 207
 function disconnectAndShowFeedback(requestFeedback) {
208 208
     APP.UI.hideRingOverLay();
209 209
     connection.disconnect();
210
-    APP.API.sendPostisMessage({
211
-        method: 'video-conference-left',
212
-        params: {roomName: APP.conference.roomName}
213
-    });
210
+    APP.API.notifyConferenceLeft(APP.conference.roomName);
214 211
     if (requestFeedback) {
215 212
         return APP.UI.requestFeedback();
216 213
     } else {
@@ -456,6 +453,14 @@ export default {
456 453
     videoMuted: false,
457 454
     isSharingScreen: false,
458 455
     isDesktopSharingEnabled: false,
456
+    /*
457
+     * Whether the local "raisedHand" flag is on.
458
+     */
459
+    isHandRaised: false,
460
+    /*
461
+     * Whether the local participant is the dominant speaker in the conference.
462
+     */
463
+    isDominantSpeaker: false,
459 464
     /**
460 465
      * Open new connection and join to the conference.
461 466
      * @param {object} options
@@ -496,9 +501,6 @@ export default {
496 501
                 this._createRoom(tracks);
497 502
                 this.isDesktopSharingEnabled =
498 503
                     JitsiMeetJS.isDesktopSharingEnabled();
499
-                if(this.isDesktopSharingEnabled)
500
-                    APP.API.addPostisMessageListener('toggle-share-screen',
501
-                        () => this.toggleScreenSharing());
502 504
 
503 505
                 // if user didn't give access to mic or camera or doesn't have
504 506
                 // them at all, we disable corresponding toolbar buttons
@@ -939,10 +941,7 @@ export default {
939 941
         // add local streams when joined to the conference
940 942
         room.on(ConferenceEvents.CONFERENCE_JOINED, () => {
941 943
             APP.UI.mucJoined();
942
-            APP.API.sendPostisMessage({
943
-              method: 'video-conference-joined',
944
-              params: {roomName: APP.conference.roomName}
945
-            });
944
+            APP.API.notifyConferenceJoined(APP.conference.roomName);
946 945
         });
947 946
 
948 947
         room.on(
@@ -1049,6 +1048,16 @@ export default {
1049 1048
             APP.UI.handleLastNEndpoints(ids, enteringIds);
1050 1049
         });
1051 1050
         room.on(ConferenceEvents.DOMINANT_SPEAKER_CHANGED, (id) => {
1051
+            if (this.isLocalId(id)) {
1052
+                this.isDominantSpeaker = true;
1053
+                this.setRaisedHand(false);
1054
+            } else {
1055
+                this.isDominantSpeaker = false;
1056
+                var participant = room.getParticipantById(id);
1057
+                if (participant) {
1058
+                    APP.UI.setRaisedHandStatus(participant, false);
1059
+                }
1060
+            }
1052 1061
             APP.UI.markDominantSpeaker(id);
1053 1062
         });
1054 1063
 
@@ -1071,6 +1080,13 @@ export default {
1071 1080
             APP.UI.changeDisplayName(id, displayName);
1072 1081
         });
1073 1082
 
1083
+        room.on(ConferenceEvents.PARTICIPANT_PROPERTY_CHANGED,
1084
+                (participant, name, oldValue, newValue) => {
1085
+            if (name === "raisedHand") {
1086
+                APP.UI.setRaisedHandStatus(participant, newValue);
1087
+            }
1088
+        });
1089
+
1074 1090
         room.on(ConferenceEvents.RECORDER_STATE_CHANGED, (status, error) => {
1075 1091
             console.log("Received recorder status change: ", status, error);
1076 1092
             APP.UI.updateRecordingState(status);
@@ -1471,5 +1487,30 @@ export default {
1471 1487
                 mediaDeviceHelper.setCurrentMediaDevices(devices);
1472 1488
                 APP.UI.onAvailableDevicesChanged(devices);
1473 1489
             });
1490
+    },
1491
+
1492
+    /**
1493
+     * Toggles the local "raised hand" status, if the current state allows
1494
+     * toggling.
1495
+     */
1496
+    maybeToggleRaisedHand() {
1497
+        // If we are the dominant speaker, we don't enable "raise hand".
1498
+        if (this.isHandRaised || !this.isDominantSpeaker) {
1499
+            this.setRaisedHand(!this.isHandRaised);
1500
+        }
1501
+    },
1502
+
1503
+    /**
1504
+     * Sets the local "raised hand" status to a particular value.
1505
+     */
1506
+    setRaisedHand(raisedHand) {
1507
+        if (raisedHand !== this.isHandRaised)
1508
+        {
1509
+            this.isHandRaised = raisedHand;
1510
+            // Advertise the updated status
1511
+            room.setLocalParticipantProperty("raisedHand", raisedHand);
1512
+            // Update the view
1513
+            APP.UI.setLocalRaisedHandStatus(raisedHand);
1514
+        }
1474 1515
     }
1475 1516
 };

+ 2
- 2
css/videolayout_default.css Просмотреть файл

@@ -310,7 +310,7 @@
310 310
     z-index: 3;
311 311
 }
312 312
 
313
-.videocontainer>span.dominantspeakerindicator {
313
+.videocontainer>span.indicator {
314 314
     bottom: 0px;
315 315
     left: 0px;
316 316
     width: 25px;
@@ -327,7 +327,7 @@
327 327
     border: 0px;
328 328
 }
329 329
 
330
-#speakerindicatoricon {
330
+#indicatoricon {
331 331
     padding-top: 5px;
332 332
 }
333 333
 

+ 1
- 1
debian/control Просмотреть файл

@@ -3,7 +3,7 @@ Section: net
3 3
 Priority: extra
4 4
 Maintainer: Jitsi Team <dev@jitsi.org>
5 5
 Uploaders: Emil Ivov <emcho@jitsi.org>, Damian Minkov <damencho@jitsi.org>
6
-Build-Depends: debhelper (>= 8.0.0), yui-compressor
6
+Build-Depends: debhelper (>= 8.0.0)
7 7
 Standards-Version: 3.9.6
8 8
 Homepage: https://jitsi.org/meet
9 9
 

+ 28
- 9
doc/api.md Просмотреть файл

@@ -20,13 +20,13 @@ The next step for embedding Jitsi Meet is to create the Jitsi Meet API object
20 20
     var height = 700;
21 21
     var api = new JitsiMeetExternalAPI(domain, room, width, height);
22 22
 </script>
23
-``` 
23
+```
24 24
 You can paste that lines in your html code where you want to be placed the Jitsi Meet conference
25 25
 or you can specify the parent HTML element for the Jitsi Meet conference in the JitsiMeetExternalAPI
26 26
 constructor.
27 27
 ```javascript
28 28
     var api = new JitsiMeetExternalAPI(domain, room, width, height, htmlElement);
29
-``` 
29
+```
30 30
 If you don't specify room the user will enter in new conference with random room name.
31 31
 
32 32
 You can overwrite options set in config.js and interface_config.js. For example, to enable the film-strip-only interface mode and disable simulcast, you can use:
@@ -34,24 +34,24 @@ You can overwrite options set in config.js and interface_config.js. For example,
34 34
     var configOverwrite = {enableSimulcast: false};
35 35
     var interfaceConfigOverwrite = {filmStripOnly: true};
36 36
     var api = new JitsiMeetExternalAPI(domain, room, width, height, htmlElement, true, configOverwrite, interfaceConfigOverwrite);
37
-``` 
37
+```
38 38
 
39 39
 Controlling embedded Jitsi Meet Conference
40 40
 =========
41 41
 
42 42
 You can control the embedded Jitsi Meet conference using the JitsiMeetExternalAPI object.
43 43
 
44
-You can send command to Jitsi Meet conference using ```executeCommand```. 
44
+You can send command to Jitsi Meet conference using ```executeCommand```.
45 45
 ```
46 46
 api.executeCommand(command, arguments)
47 47
 ```
48 48
 The ```command``` parameter is String object with the name of the command.
49
-The ```arguments``` parameter is array with the arguments required by the command. 
49
+The ```arguments``` parameter is array with the arguments required by the command.
50 50
 If no arguments are required by the command this parameter can be omitted or you can pass empty array.
51 51
 Currently we support the following commands:
52 52
 
53 53
 
54
-* **displayName** - sets the display name of the local participant. This command requires one argument - 
54
+* **displayName** - sets the display name of the local participant. This command requires one argument -
55 55
 the new display name to be set
56 56
 ```
57 57
 api.executeCommand('displayName', ['New Nickname']);
@@ -77,7 +77,12 @@ api.executeCommand('toggleChat', [])
77 77
 api.executeCommand('toggleContactList', [])
78 78
 ```
79 79
 
80
-You can also execute multiple commands using the method ```executeCommands```. 
80
+* **toggleShareScreen** - starts / stops the screen sharing. No arguments are required.
81
+```
82
+api.executeCommand('toggleShareScreen', [])
83
+```
84
+
85
+You can also execute multiple commands using the method ```executeCommands```.
81 86
 ```
82 87
 api.executeCommands(commands)
83 88
 ```
@@ -136,9 +141,23 @@ The listener will receive object with the following structure:
136 141
 jid: jid //the jid of the participant
137 142
 }
138 143
 ```
144
+* **video-conference-joined** - event notifications fired when the local user has joined the video conference.
145
+The listener will receive object with the following structure:
146
+```
147
+{
148
+roomName: room //the room name of the conference
149
+}
150
+```
151
+* **video-conference-left** - event notifications fired when the local user has left the video conference.
152
+The listener will receive object with the following structure:
153
+```
154
+{
155
+roomName: room //the room name of the conference
156
+}
157
+```
139 158
 
140 159
 You can also add multiple event listeners by using ```addEventListeners```.
141
-This method requires one argument of type Object. The object argument must 
160
+This method requires one argument of type Object. The object argument must
142 161
 have keys with the names of the events and values the listeners of the events.
143 162
 
144 163
 ```
@@ -173,4 +192,4 @@ You can remove the embedded Jitsi Meet Conference with the following code:
173 192
 api.dispose()
174 193
 ```
175 194
 
176
-It is a good practice to remove the conference before the page is unloaded. 
195
+It is a good practice to remove the conference before the page is unloaded.

+ 5
- 0
doc/debian/jitsi-meet/jitsi-meet.example Просмотреть файл

@@ -33,6 +33,11 @@ server {
33 33
         ssi on;
34 34
     }
35 35
 
36
+    # Backward compatibility
37
+    location ~ /external_api.* {
38
+        root /usr/share/jitsi-meet/libs;
39
+    }
40
+
36 41
     # BOSH
37 42
     location /http-bind {
38 43
         proxy_pass      http://localhost:5280/http-bind;

+ 0
- 378
external_api.js Просмотреть файл

@@ -1,378 +0,0 @@
1
-/**
2
- * Implements API class that embeds Jitsi Meet in external applications.
3
- */
4
-var JitsiMeetExternalAPI = (function()
5
-{
6
-    /**
7
-     * The minimum width for the Jitsi Meet frame
8
-     * @type {number}
9
-     */
10
-    var MIN_WIDTH = 790;
11
-
12
-    /**
13
-     * The minimum height for the Jitsi Meet frame
14
-     * @type {number}
15
-     */
16
-    var MIN_HEIGHT = 300;
17
-
18
-    /**
19
-     * Constructs new API instance. Creates iframe element that loads
20
-     * Jitsi Meet.
21
-     * @param domain the domain name of the server that hosts the conference
22
-     * @param room_name the name of the room to join
23
-     * @param width width of the iframe
24
-     * @param height height of the iframe
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.
28
-     * @param noSsl if the value is true https won't be used
29
-     * @constructor
30
-     */
31
-    function JitsiMeetExternalAPI(domain, room_name, width, height, parentNode,
32
-        configOverwrite, interfaceConfigOverwrite, noSsl) {
33
-        if (!width || width < MIN_WIDTH)
34
-            width = MIN_WIDTH;
35
-        if (!height || height < MIN_HEIGHT)
36
-            height = MIN_HEIGHT;
37
-
38
-        this.parentNode = null;
39
-        if (parentNode) {
40
-            this.parentNode = parentNode;
41
-        } else {
42
-            var scriptTag = document.scripts[document.scripts.length - 1];
43
-            this.parentNode = scriptTag.parentNode;
44
-        }
45
-
46
-        this.iframeHolder =
47
-            this.parentNode.appendChild(document.createElement("div"));
48
-        this.iframeHolder.id = "jitsiConference" + JitsiMeetExternalAPI.id;
49
-        if(width)
50
-            this.iframeHolder.style.width = width + "px";
51
-        if(height)
52
-            this.iframeHolder.style.height = height + "px";
53
-        this.frameName = "jitsiConferenceFrame" + JitsiMeetExternalAPI.id;
54
-        this.url = (noSsl) ? "http" : "https" +"://" + domain + "/";
55
-        if(room_name)
56
-            this.url += room_name;
57
-        this.url += "#external=true";
58
-
59
-        var key;
60
-        if (configOverwrite) {
61
-            for (key in configOverwrite) {
62
-                if (!configOverwrite.hasOwnProperty(key) ||
63
-                    typeof key !== 'string')
64
-                    continue;
65
-                this.url += "&config." + key + "=" + configOverwrite[key];
66
-            }
67
-        }
68
-
69
-        if (interfaceConfigOverwrite) {
70
-            for (key in interfaceConfigOverwrite) {
71
-                if (!interfaceConfigOverwrite.hasOwnProperty(key) ||
72
-                    typeof key !== 'string')
73
-                    continue;
74
-                this.url += "&interfaceConfig." + key + "=" +
75
-                    interfaceConfigOverwrite[key];
76
-            }
77
-        }
78
-
79
-        JitsiMeetExternalAPI.id++;
80
-
81
-        this.frame = document.createElement("iframe");
82
-        this.frame.src = this.url;
83
-        this.frame.name = this.frameName;
84
-        this.frame.id = this.frameName;
85
-        this.frame.width = "100%";
86
-        this.frame.height = "100%";
87
-        this.frame.setAttribute("allowFullScreen","true");
88
-        this.frame = this.iframeHolder.appendChild(this.frame);
89
-
90
-
91
-        this.frameLoaded = false;
92
-        this.initialCommands = [];
93
-        this.eventHandlers = {};
94
-        this.initListeners();
95
-    }
96
-
97
-    /**
98
-     * Last id of api object
99
-     * @type {number}
100
-     */
101
-    JitsiMeetExternalAPI.id = 0;
102
-
103
-    /**
104
-     * Sends the passed object to Jitsi Meet
105
-     * @param object the object to be sent
106
-     */
107
-    JitsiMeetExternalAPI.prototype.sendMessage = function(object) {
108
-        if (this.frameLoaded) {
109
-            this.frame.contentWindow.postMessage(
110
-                JSON.stringify(object), this.frame.src);
111
-        }
112
-        else {
113
-            this.initialCommands.push(object);
114
-        }
115
-
116
-    };
117
-
118
-    /**
119
-     * Executes command. The available commands are:
120
-     * displayName - sets the display name of the local participant to the value
121
-     * passed in the arguments array.
122
-     * toggleAudio - mutes / unmutes audio with no arguments
123
-     * toggleVideo - mutes / unmutes video with no arguments
124
-     * filmStrip - hides / shows the film strip with no arguments
125
-     * If the command doesn't require any arguments the parameter should be set
126
-     * to empty array or it may be omitted.
127
-     * @param name the name of the command
128
-     * @param arguments array of arguments
129
-     */
130
-    JitsiMeetExternalAPI.prototype.executeCommand = function(name,
131
-                                                             argumentsList) {
132
-        var argumentsArray = argumentsList;
133
-        if (!argumentsArray)
134
-            argumentsArray = [];
135
-        var object = {type: "command", action: "execute"};
136
-        object[name] = argumentsArray;
137
-        this.sendMessage(object);
138
-    };
139
-
140
-    /**
141
-     * Executes commands. The available commands are:
142
-     * displayName - sets the display name of the local participant to the value
143
-     * passed in the arguments array.
144
-     * toggleAudio - mutes / unmutes audio with no arguments
145
-     * toggleVideo - mutes / unmutes video with no arguments
146
-     * filmStrip - hides / shows the film strip with no arguments
147
-     * @param object the object with commands to be executed. The keys of the
148
-     * object are the commands that will be executed and the values are the
149
-     * arguments for the command.
150
-     */
151
-    JitsiMeetExternalAPI.prototype.executeCommands = function (object) {
152
-        object.type = "command";
153
-        object.action = "execute";
154
-        this.sendMessage(object);
155
-    };
156
-
157
-    /**
158
-     * Adds event listeners to Meet Jitsi. The object key should be the name of
159
-     * the event and value - the listener.
160
-     * Currently we support the following
161
-     * events:
162
-     * incomingMessage - receives event notifications about incoming
163
-     * messages. The listener will receive object with the following structure:
164
-     * {{
165
-     *  "from": from,//JID of the user that sent the message
166
-     *  "nick": nick,//the nickname of the user that sent the message
167
-     *  "message": txt//the text of the message
168
-     * }}
169
-     * outgoingMessage - receives event notifications about outgoing
170
-     * messages. The listener will receive object with the following structure:
171
-     * {{
172
-     *  "message": txt//the text of the message
173
-     * }}
174
-     * displayNameChanged - receives event notifications about display name
175
-     * change. The listener will receive object with the following structure:
176
-     * {{
177
-     * jid: jid,//the JID of the participant that changed his display name
178
-     * displayname: displayName //the new display name
179
-     * }}
180
-     * participantJoined - receives event notifications about new participant.
181
-     * The listener will receive object with the following structure:
182
-     * {{
183
-     * jid: jid //the jid of the participant
184
-     * }}
185
-     * participantLeft - receives event notifications about the participant that
186
-     * left the room.
187
-     * The listener will receive object with the following structure:
188
-     * {{
189
-     * jid: jid //the jid of the participant
190
-     * }}
191
-     * @param object
192
-     */
193
-    JitsiMeetExternalAPI.prototype.addEventListeners
194
-        = function (object) {
195
-
196
-        var message = {type: "event", action: "add", events: []};
197
-        for(var i in object)
198
-        {
199
-            message.events.push(i);
200
-            this.eventHandlers[i] = object[i];
201
-        }
202
-        this.sendMessage(message);
203
-    };
204
-
205
-    /**
206
-     * Adds event listeners to Meet Jitsi. Currently we support the following
207
-     * events:
208
-     * incomingMessage - receives event notifications about incoming
209
-     * messages. The listener will receive object with the following structure:
210
-     * {{
211
-     *  "from": from,//JID of the user that sent the message
212
-     *  "nick": nick,//the nickname of the user that sent the message
213
-     *  "message": txt//the text of the message
214
-     * }}
215
-     * outgoingMessage - receives event notifications about outgoing
216
-     * messages. The listener will receive object with the following structure:
217
-     * {{
218
-     *  "message": txt//the text of the message
219
-     * }}
220
-     * displayNameChanged - receives event notifications about display name
221
-     * change. The listener will receive object with the following structure:
222
-     * {{
223
-     * jid: jid,//the JID of the participant that changed his display name
224
-     * displayname: displayName //the new display name
225
-     * }}
226
-     * participantJoined - receives event notifications about new participant.
227
-     * The listener will receive object with the following structure:
228
-     * {{
229
-     * jid: jid //the jid of the participant
230
-     * }}
231
-     * participantLeft - receives event notifications about participant the that
232
-     * left the room.
233
-     * The listener will receive object with the following structure:
234
-     * {{
235
-     * jid: jid //the jid of the participant
236
-     * }}
237
-     * @param event the name of the event
238
-     * @param listener the listener
239
-     */
240
-    JitsiMeetExternalAPI.prototype.addEventListener
241
-        = function (event, listener) {
242
-
243
-        var message = {type: "event", action: "add", events: [event]};
244
-        this.eventHandlers[event] = listener;
245
-        this.sendMessage(message);
246
-    };
247
-
248
-    /**
249
-     * Removes event listener.
250
-     * @param event the name of the event.
251
-     */
252
-    JitsiMeetExternalAPI.prototype.removeEventListener
253
-        = function (event) {
254
-        if(!this.eventHandlers[event])
255
-        {
256
-            console.error("The event " + event + " is not registered.");
257
-            return;
258
-        }
259
-        var message = {type: "event", action: "remove", events: [event]};
260
-        delete this.eventHandlers[event];
261
-        this.sendMessage(message);
262
-    };
263
-
264
-    /**
265
-     * Removes event listeners.
266
-     * @param events array with the names of the events.
267
-     */
268
-    JitsiMeetExternalAPI.prototype.removeEventListeners
269
-        = function (events) {
270
-        var eventsArray = [];
271
-        for(var i = 0; i < events.length; i++)
272
-        {
273
-            var event = events[i];
274
-            if(!this.eventHandlers[event])
275
-            {
276
-                console.error("The event " + event + " is not registered.");
277
-                continue;
278
-            }
279
-            delete this.eventHandlers[event];
280
-            eventsArray.push(event);
281
-        }
282
-
283
-        if(eventsArray.length > 0)
284
-        {
285
-            this.sendMessage(
286
-                {type: "event", action: "remove", events: eventsArray});
287
-        }
288
-
289
-    };
290
-
291
-    /**
292
-     * Processes message events sent from Jitsi Meet
293
-     * @param event the event
294
-     */
295
-    JitsiMeetExternalAPI.prototype.processMessage = function(event) {
296
-        var message;
297
-        try {
298
-            message = JSON.parse(event.data);
299
-        } catch (e) {}
300
-
301
-        if(!message.type) {
302
-            console.error("Message without type is received.");
303
-            return;
304
-        }
305
-        switch (message.type) {
306
-            case "system":
307
-                if(message.loaded) {
308
-                    this.onFrameLoaded();
309
-                }
310
-                break;
311
-            case "event":
312
-                if(message.action != "result" ||
313
-                    !message.event || !this.eventHandlers[message.event]) {
314
-                    console.warn("The received event cannot be parsed.");
315
-                    return;
316
-                }
317
-                this.eventHandlers[message.event](message.result);
318
-                break;
319
-            default :
320
-                console.error("Unknown message type.");
321
-                return;
322
-        }
323
-    };
324
-
325
-    /**
326
-     * That method is called when the Jitsi Meet is loaded. Executes saved
327
-     * commands that are send before the frame was loaded.
328
-     */
329
-    JitsiMeetExternalAPI.prototype.onFrameLoaded = function () {
330
-        this.frameLoaded = true;
331
-        for (var i = 0; i < this.initialCommands.length; i++) {
332
-            this.sendMessage(this.initialCommands[i]);
333
-        }
334
-        this.initialCommands = null;
335
-    };
336
-
337
-    /**
338
-     * Setups the listener for message events from Jitsi Meet.
339
-     */
340
-    JitsiMeetExternalAPI.prototype.initListeners = function () {
341
-        var self = this;
342
-        this.eventListener = function (event) {
343
-            self.processMessage(event);
344
-        };
345
-        if (window.addEventListener) {
346
-            window.addEventListener('message',
347
-                this.eventListener, false);
348
-        }
349
-        else {
350
-            window.attachEvent('onmessage', this.eventListener);
351
-        }
352
-    };
353
-
354
-    /**
355
-     * Removes the listeners and removes the Jitsi Meet frame.
356
-     */
357
-    JitsiMeetExternalAPI.prototype.dispose = function () {
358
-        if (window.removeEventListener) {
359
-            window.removeEventListener('message',
360
-                this.eventListener, false);
361
-        }
362
-        else {
363
-            window.detachEvent('onmessage',
364
-                this.eventListener);
365
-        }
366
-        var frame = document.getElementById(this.frameName);
367
-        if(frame)
368
-            frame.src = 'about:blank';
369
-        var self = this;
370
-        window.setTimeout(function () {
371
-                self.iframeHolder.removeChild(self.frame);
372
-                self.iframeHolder.parentNode.removeChild(self.iframeHolder);
373
-        }, 10);
374
-    };
375
-
376
-    return JitsiMeetExternalAPI;
377
-
378
-})();

+ 1
- 1
index.html Просмотреть файл

@@ -241,7 +241,7 @@
241 241
             <div class="arrow-up"></div>
242 242
             <input type="text" id="setDisplayName" data-i18n="[placeholder]settings.name" placeholder="Name">
243 243
             <input type="text" id="setEmail" placeholder="E-Mail">
244
-            <input type="text" id="setAvatarUrl" placeholder="Avatar URL" data-i18n="[placeholder]settings.avatar">
244
+            <input type="text" id="setAvatarUrl" placeholder="Avatar URL" data-i18n="[placeholder]settings.avatarUrl">
245 245
             <select id="languages_selectbox"></select>
246 246
             <div id = "startMutedOptions">
247 247
                 <label class = "startMutedLabel">

+ 3
- 1
lang/main.json Просмотреть файл

@@ -8,6 +8,7 @@
8 8
     "participant": "Participant",
9 9
     "me": "me",
10 10
     "speaker": "Speaker",
11
+    "raisedHand": "Would like to speak",
11 12
     "defaultNickname": "ex. Jane Pink",
12 13
     "defaultLink": "e.g. __url__",
13 14
     "calling": "Calling __name__ ...",
@@ -164,7 +165,8 @@
164 165
         "grantedTo": "Moderator rights granted to __to__!",
165 166
         "grantedToUnknown": "Moderator rights granted to $t(somebody)!",
166 167
         "muted": "You have started the conversation muted.",
167
-        "mutedTitle": "You're muted!"
168
+        "mutedTitle": "You're muted!",
169
+        "raisedHand": "Would like to speak."
168 170
     },
169 171
     "dialog": {
170 172
         "kickMessage": "Ouch! You have been kicked out of the meet!",

+ 107
- 166
modules/API/API.js Просмотреть файл

@@ -1,11 +1,11 @@
1
-/* global APP */
1
+/* global APP, getConfigParamsFromUrl */
2 2
 /**
3 3
  * Implements API class that communicates with external api class
4 4
  * and provides interface to access Jitsi Meet features by external
5 5
  * applications that embed Jitsi Meet
6 6
  */
7 7
 
8
- import postis from 'postis';
8
+import postisInit from 'postis';
9 9
 
10 10
 /**
11 11
  * List of the available commands.
@@ -20,36 +20,41 @@
20 20
  */
21 21
 let commands = {};
22 22
 
23
+let hashParams = getConfigParamsFromUrl();
24
+
23 25
 /**
24
- * Object that will execute sendMessage
26
+ * JitsiMeetExternalAPI id - unique for a webpage.
25 27
  */
26
-let target = window.opener ? window.opener : window.parent;
28
+let jitsi_meet_external_api_id = hashParams.jitsi_meet_external_api_id;
27 29
 
28 30
 /**
29
- * Array of functions that are going to receive the objects passed to this
30
- * window
31
+ * Object that will execute sendMessage
31 32
  */
32
-let messageListeners = [];
33
+let target = window.opener ? window.opener : window.parent;
33 34
 
34 35
 /**
35
- * Current status (enabled/disabled) of Postis.
36
+ * Postis instance. Used to communicate with the external application.
36 37
  */
37
-let enablePostis = false;
38
+let postis;
38 39
 
39 40
 /**
40
- * Current status (enabled/disabled) of Post Message API.
41
+ * Current status (enabled/disabled) of API.
41 42
  */
42
-let enablePostMessage = false;
43
+let enabled = false;
43 44
 
44 45
 function initCommands() {
45 46
     commands = {
46
-        displayName: APP.UI.inputDisplayNameHandler,
47
-        toggleAudio: APP.conference.toggleAudioMuted,
48
-        toggleVideo: APP.conference.toggleVideoMuted,
49
-        toggleFilmStrip: APP.UI.toggleFilmStrip,
50
-        toggleChat: APP.UI.toggleChat,
51
-        toggleContactList: APP.UI.toggleContactList
47
+        "display-name": APP.UI.inputDisplayNameHandler,
48
+        "toggle-audio": APP.conference.toggleAudioMuted,
49
+        "toggle-video": APP.conference.toggleVideoMuted,
50
+        "toggle-film-strip": APP.UI.toggleFilmStrip,
51
+        "toggle-chat": APP.UI.toggleChat,
52
+        "toggle-contact-list": APP.UI.toggleContactList,
53
+        "toggle-share-screen": APP.conference.toggleScreenSharing
52 54
     };
55
+    Object.keys(commands).forEach(function (key) {
56
+        postis.listen(key, commands[key]);
57
+    });
53 58
 }
54 59
 
55 60
 
@@ -57,95 +62,34 @@ function initCommands() {
57 62
  * Maps the supported events and their status
58 63
  * (true it the event is enabled and false if it is disabled)
59 64
  * @type {{
60
- *              incomingMessage: boolean,
61
- *              outgoingMessage: boolean,
62
- *              displayNameChange: boolean,
63
- *              participantJoined: boolean,
64
- *              participantLeft: boolean
65
+ *              incoming-message: boolean,
66
+ *              outgoing-message: boolean,
67
+ *              display-name-change: boolean,
68
+ *              participant-left: boolean,
69
+ *              participant-joined: boolean,
70
+ *              video-conference-left: boolean,
71
+ *              video-conference-joined: boolean
65 72
  *      }}
66 73
  */
67 74
 const events = {
68
-    incomingMessage: false,
69
-    outgoingMessage:false,
70
-    displayNameChange: false,
71
-    participantJoined: false,
72
-    participantLeft: false
75
+    "incoming-message": false,
76
+    "outgoing-message":false,
77
+    "display-name-change": false,
78
+    "participant-joined": false,
79
+    "participant-left": false,
80
+    "video-conference-joined": false,
81
+    "video-conference-left": false
73 82
 };
74 83
 
75
-/**
76
- * Processes commands from external application.
77
- * @param message the object with the command
78
- */
79
-function processCommand(message) {
80
-    if (message.action != "execute") {
81
-        console.error("Unknown action of the message");
82
-        return;
83
-    }
84
-    for (var key in message) {
85
-        if(commands[key])
86
-            commands[key].apply(null, message[key]);
87
-    }
88
-}
89
-
90
-/**
91
- * Processes events objects from external applications
92
- * @param event the event
93
- */
94
-function processEvent(event) {
95
-    if (!event.action) {
96
-        console.error("Event with no action is received.");
97
-        return;
98
-    }
99
-
100
-    var i = 0;
101
-    switch(event.action) {
102
-        case "add":
103
-            for (; i < event.events.length; i++) {
104
-                events[event.events[i]] = true;
105
-            }
106
-            break;
107
-        case "remove":
108
-            for (; i < event.events.length; i++) {
109
-                events[event.events[i]] = false;
110
-            }
111
-            break;
112
-        default:
113
-            console.error("Unknown action for event.");
114
-    }
115
-}
116
-
117
-/**
118
- * Processes a message event from the external application
119
- * @param event the message event
120
- */
121
-function processMessage(event) {
122
-    var message;
123
-    try {
124
-        message = JSON.parse(event.data);
125
-    } catch (e) {
126
-        console.error("Cannot parse data", event.data);
127
-        return;
128
-    }
129
-
130
-    switch (message.type) {
131
-        case "command":
132
-            processCommand(message);
133
-            break;
134
-        case "event":
135
-            processEvent(message);
136
-            break;
137
-        default:
138
-            console.warn("Unknown message type");
139
-    }
140
-}
141
-
142 84
 /**
143 85
  * Sends message to the external application.
144
- * @param object {object} the object that will be sent as JSON string
86
+ * @param message {object}
87
+ * @param method {string}
88
+ * @param params {object} the object that will be sent as JSON string
145 89
  */
146
-function sendMessage(object) {
147
-    if(enablePostMessage)
148
-        target.postMessage(JSON.stringify(object), "*");
90
+function sendMessage(message) {
91
+    if(enabled)
92
+        postis.send(message);
149 93
 }
150 94
 
151 95
 /**
@@ -153,8 +97,7 @@ function sendMessage(object) {
153 97
  * @returns {boolean}
154 98
  */
155 99
 function isEnabled () {
156
-    let hash = location.hash;
157
-    return !!(hash && hash.indexOf("external=true") > -1 && window.postMessage);
100
+    return (typeof jitsi_meet_external_api_id === "number");
158 101
 }
159 102
 
160 103
 /**
@@ -173,13 +116,25 @@ function isEventEnabled (name) {
173 116
  * @param object data associated with the event
174 117
  */
175 118
 function triggerEvent (name, object) {
176
-    if (isEventEnabled(name) && enablePostMessage) {
177
-        sendMessage({
178
-            type: "event",
179
-            action: "result",
180
-            event: name,
181
-            result: object
182
-        });
119
+    if(isEventEnabled(name))
120
+        sendMessage({method: name, params: object});
121
+}
122
+
123
+/**
124
+ * Handles system messages. (for example: enable/disable events)
125
+ * @param message {object} the message
126
+ */
127
+function onSystemMessage(message) {
128
+    switch (message.type) {
129
+        case "eventStatus":
130
+            if(!message.name || !message.value) {
131
+                console.warn("Unknown system message format", message);
132
+                break;
133
+            }
134
+            events[message.name] = message.value;
135
+            break;
136
+        default:
137
+            console.warn("Unknown system message type", message);
183 138
     }
184 139
 }
185 140
 
@@ -190,33 +145,27 @@ export default {
190 145
      * It also sends a message to the external application that APIConnector
191 146
      * is initialized.
192 147
      * @param options {object}
193
-     * @param enablePostis {boolean} if true the postis npm
194
-     * package for comminication with the parent window will be enabled.
195
-     * @param enablePostMessage {boolean} if true the postMessageAPI for
196
-     * comminication with the parent window will be enabled.
148
+     * @param forceEnable {boolean} if true the module will be enabled.
149
+     * @param enabledEvents {array} array of events that should be enabled.
197 150
      */
198
-    init: function (options = {}) {
199
-        options.enablePostMessage = options.enablePostMessage || isEnabled();
200
-        if (!options.enablePostis &&
201
-            !options.enablePostMessage) {
151
+    init (options = {}) {
152
+        if(!isEnabled() && !options.forceEnable)
202 153
             return;
203
-        }
204
-        enablePostis = options.enablePostis;
205
-        enablePostMessage = options.enablePostMessage;
206 154
 
207
-        if(enablePostMessage) {
208
-            initCommands();
209
-            if (window.addEventListener) {
210
-                window.addEventListener('message', processMessage, false);
211
-            } else {
212
-                window.attachEvent('onmessage', processMessage);
213
-            }
214
-            sendMessage({type: "system", loaded: true});
215
-        }
216
-
217
-        if(enablePostis) {
218
-            this.postis = postis({window: target});
219
-        }
155
+        enabled = true;
156
+        if(options.enabledEvents)
157
+            options.enabledEvents.forEach(function (eventName) {
158
+                events[eventName] = true;
159
+            });
160
+        let postisOptions = {
161
+            window: target
162
+        };
163
+        if(typeof jitsi_meet_external_api_id === "number")
164
+            postisOptions.scope
165
+                = "jitsi_meet_external_api_" + jitsi_meet_external_api_id;
166
+        postis = postisInit(postisOptions);
167
+        postis.listen("jitsiSystemMessage", onSystemMessage);
168
+        initCommands();
220 169
     },
221 170
 
222 171
     /**
@@ -224,28 +173,7 @@ export default {
224 173
      * @param {string} body message body
225 174
      */
226 175
     notifySendingChatMessage (body) {
227
-        triggerEvent("outgoingMessage", {"message": body});
228
-    },
229
-
230
-    /**
231
-     * Sends message to the external application.
232
-     * @param options {object}
233
-     * @param method {string}
234
-     * @param params {object} the object that will be sent as JSON string
235
-     */
236
-    sendPostisMessage(options) {
237
-        if(enablePostis)
238
-            this.postis.send(options);
239
-    },
240
-
241
-    /**
242
-     * Adds listener for Postis messages.
243
-     * @param method {string} postis mehtod
244
-     * @param listener {function}
245
-     */
246
-    addPostisMessageListener (method, listener) {
247
-        if(enablePostis)
248
-            this.postis.listen(method, listener);
176
+        triggerEvent("outgoing-message", {"message": body});
249 177
     },
250 178
 
251 179
     /**
@@ -262,7 +190,7 @@ export default {
262 190
         }
263 191
 
264 192
         triggerEvent(
265
-            "incomingMessage",
193
+            "incoming-message",
266 194
             {"from": id, "nick": nick, "message": body, "stamp": ts}
267 195
         );
268 196
     },
@@ -273,7 +201,7 @@ export default {
273 201
      * @param {string} id user id
274 202
      */
275 203
     notifyUserJoined (id) {
276
-        triggerEvent("participantJoined", {id});
204
+        triggerEvent("participant-joined", {id});
277 205
     },
278 206
 
279 207
     /**
@@ -282,7 +210,7 @@ export default {
282 210
      * @param {string} id user id
283 211
      */
284 212
     notifyUserLeft (id) {
285
-        triggerEvent("participantLeft", {id});
213
+        triggerEvent("participant-left", {id});
286 214
     },
287 215
 
288 216
     /**
@@ -292,21 +220,34 @@ export default {
292 220
      * @param {string} displayName user nickname
293 221
      */
294 222
     notifyDisplayNameChanged (id, displayName) {
295
-        triggerEvent("displayNameChange", {id, displayname: displayName});
223
+        triggerEvent("display-name-change", {id, displayname: displayName});
224
+    },
225
+
226
+    /**
227
+     * Notify external application (if API is enabled) that
228
+     * user changed their nickname.
229
+     * @param {string} id user id
230
+     * @param {string} displayName user nickname
231
+     */
232
+    notifyConferenceJoined (room) {
233
+        triggerEvent("video-conference-joined", {roomName: room});
234
+    },
235
+
236
+    /**
237
+     * Notify external application (if API is enabled) that
238
+     * user changed their nickname.
239
+     * @param {string} id user id
240
+     * @param {string} displayName user nickname
241
+     */
242
+    notifyConferenceLeft (room) {
243
+        triggerEvent("video-conference-left", {roomName: room});
296 244
     },
297 245
 
298 246
     /**
299 247
      * Removes the listeners.
300 248
      */
301 249
     dispose: function () {
302
-        if (enablePostMessage) {
303
-            if (window.removeEventListener) {
304
-                window.removeEventListener("message", processMessage, false);
305
-            } else {
306
-                window.detachEvent('onmessage', processMessage);
307
-            }
308
-        }
309
-        if(enablePostis)
310
-            this.postis.destroy();
250
+        if(enabled)
251
+            postis.destroy();
311 252
     }
312 253
 };

+ 359
- 0
modules/API/external/external_api.js Просмотреть файл

@@ -0,0 +1,359 @@
1
+/**
2
+ * Implements API class that embeds Jitsi Meet in external applications.
3
+ */
4
+
5
+var postisInit = require("postis");
6
+
7
+/**
8
+ * The minimum width for the Jitsi Meet frame
9
+ * @type {number}
10
+ */
11
+var MIN_WIDTH = 790;
12
+
13
+/**
14
+ * The minimum height for the Jitsi Meet frame
15
+ * @type {number}
16
+ */
17
+var MIN_HEIGHT = 300;
18
+
19
+/**
20
+ * Last id of api object
21
+ * @type {number}
22
+ */
23
+var id = 0;
24
+
25
+/**
26
+ * Maps the names of the commands expected by the API with the name of the
27
+ * commands expected by jitsi-meet
28
+ */
29
+var commands = {
30
+    "displayName": "display-name",
31
+    "toggleAudio": "toggle-audio",
32
+    "toggleVideo": "toggle-video",
33
+    "toggleFilmStrip": "toggle-film-strip",
34
+    "toggleChat": "toggle-chat",
35
+    "toggleContactList": "toggle-contact-list",
36
+    "toggleShareScreen": "toggle-share-screen"
37
+};
38
+
39
+/**
40
+ * Maps the names of the events expected by the API with the name of the
41
+ * events expected by jitsi-meet
42
+ */
43
+var events = {
44
+    "incomingMessage": "incoming-message",
45
+    "outgoingMessage": "outgoing-message",
46
+    "displayNameChange": "display-name-change",
47
+    "participantJoined": "participant-joined",
48
+    "participantLeft": "participant-left",
49
+    "videoConferenceJoined": "video-conference-joined",
50
+    "videoConferenceLeft": "video-conference-left"
51
+};
52
+
53
+/**
54
+ * Sends the passed object to Jitsi Meet
55
+ * @param postis {Postis object} the postis instance that is going to be used
56
+ * to send the message
57
+ * @param object the object to be sent
58
+ * - method {sting}
59
+ * - params {object}
60
+ */
61
+function sendMessage(postis, object) {
62
+    postis.send(object);
63
+}
64
+
65
+/**
66
+ * Sends message for event enable/disable status change.
67
+ * @param postis {Postis object} the postis instance that is going to be used.
68
+ * @param event {string} the name of the event
69
+ * @param status {boolean} true - enabled; false - disabled;
70
+ */
71
+function changeEventStatus(postis, event, status) {
72
+    if(!(event in events)) {
73
+        console.error("Not supported event name.");
74
+        return;
75
+    }
76
+    sendMessage(postis, {
77
+        method: "jitsiSystemMessage",
78
+        params: {type: "eventStatus", name: events[event], value: status}
79
+    });
80
+}
81
+
82
+/**
83
+ * Constructs new API instance. Creates iframe element that loads
84
+ * Jitsi Meet.
85
+ * @param domain the domain name of the server that hosts the conference
86
+ * @param room_name the name of the room to join
87
+ * @param width width of the iframe
88
+ * @param height height of the iframe
89
+ * @param parent_node the node that will contain the iframe
90
+ * @param filmStripOnly if the value is true only the small videos will be
91
+ * visible.
92
+ * @param noSsl if the value is true https won't be used
93
+ * @constructor
94
+ */
95
+function JitsiMeetExternalAPI(domain, room_name, width, height, parentNode,
96
+    configOverwrite, interfaceConfigOverwrite, noSsl) {
97
+    if (!width || width < MIN_WIDTH)
98
+        width = MIN_WIDTH;
99
+    if (!height || height < MIN_HEIGHT)
100
+        height = MIN_HEIGHT;
101
+
102
+    this.parentNode = null;
103
+    if (parentNode) {
104
+        this.parentNode = parentNode;
105
+    } else {
106
+        var scriptTag = document.scripts[document.scripts.length - 1];
107
+        this.parentNode = scriptTag.parentNode;
108
+    }
109
+
110
+    this.iframeHolder =
111
+        this.parentNode.appendChild(document.createElement("div"));
112
+    this.iframeHolder.id = "jitsiConference" + id;
113
+    if(width)
114
+        this.iframeHolder.style.width = width + "px";
115
+    if(height)
116
+        this.iframeHolder.style.height = height + "px";
117
+    this.frameName = "jitsiConferenceFrame" + id;
118
+    this.url = (noSsl) ? "http" : "https" +"://" + domain + "/";
119
+    if(room_name)
120
+        this.url += room_name;
121
+    this.url += "#jitsi_meet_external_api_id=" + id;
122
+
123
+    var key;
124
+    if (configOverwrite) {
125
+        for (key in configOverwrite) {
126
+            if (!configOverwrite.hasOwnProperty(key) ||
127
+                typeof key !== 'string')
128
+                continue;
129
+            this.url += "&config." + key + "=" + configOverwrite[key];
130
+        }
131
+    }
132
+
133
+    if (interfaceConfigOverwrite) {
134
+        for (key in interfaceConfigOverwrite) {
135
+            if (!interfaceConfigOverwrite.hasOwnProperty(key) ||
136
+                typeof key !== 'string')
137
+                continue;
138
+            this.url += "&interfaceConfig." + key + "=" +
139
+                interfaceConfigOverwrite[key];
140
+        }
141
+    }
142
+
143
+    this.frame = document.createElement("iframe");
144
+    this.frame.src = this.url;
145
+    this.frame.name = this.frameName;
146
+    this.frame.id = this.frameName;
147
+    this.frame.width = "100%";
148
+    this.frame.height = "100%";
149
+    this.frame.setAttribute("allowFullScreen","true");
150
+    this.frame = this.iframeHolder.appendChild(this.frame);
151
+    this.postis = postisInit({
152
+        window: this.frame.contentWindow,
153
+        scope: "jitsi_meet_external_api_" + id
154
+    });
155
+
156
+    this.eventHandlers = {};
157
+
158
+    id++;
159
+}
160
+
161
+/**
162
+ * Executes command. The available commands are:
163
+ * displayName - sets the display name of the local participant to the value
164
+ * passed in the arguments array.
165
+ * toggleAudio - mutes / unmutes audio with no arguments
166
+ * toggleVideo - mutes / unmutes video with no arguments
167
+ * filmStrip - hides / shows the film strip with no arguments
168
+ * If the command doesn't require any arguments the parameter should be set
169
+ * to empty array or it may be omitted.
170
+ * @param name the name of the command
171
+ * @param arguments array of arguments
172
+ */
173
+JitsiMeetExternalAPI.prototype.executeCommand = function(name, argumentsList) {
174
+    if(!(name in commands)) {
175
+        console.error("Not supported command name.");
176
+        return;
177
+    }
178
+    var argumentsArray = argumentsList;
179
+    if (!argumentsArray)
180
+        argumentsArray = [];
181
+    sendMessage(this.postis, {method: commands[name], params: argumentsArray});
182
+};
183
+
184
+/**
185
+ * Executes commands. The available commands are:
186
+ * displayName - sets the display name of the local participant to the value
187
+ * passed in the arguments array.
188
+ * toggleAudio - mutes / unmutes audio. no arguments
189
+ * toggleVideo - mutes / unmutes video. no arguments
190
+ * filmStrip - hides / shows the film strip. no arguments
191
+ * toggleChat - hides / shows chat. no arguments.
192
+ * toggleContactList - hides / shows contact list. no arguments.
193
+ * toggleShareScreen - starts / stops screen sharing. no arguments.
194
+ * @param object the object with commands to be executed. The keys of the
195
+ * object are the commands that will be executed and the values are the
196
+ * arguments for the command.
197
+ */
198
+JitsiMeetExternalAPI.prototype.executeCommands = function(object) {
199
+    for(var key in object)
200
+        this.executeCommand(key, object[key]);
201
+};
202
+
203
+/**
204
+ * Adds event listeners to Meet Jitsi. The object key should be the name of
205
+ * the event and value - the listener.
206
+ * Currently we support the following
207
+ * events:
208
+ * incomingMessage - receives event notifications about incoming
209
+ * messages. The listener will receive object with the following structure:
210
+ * {{
211
+ *  "from": from,//JID of the user that sent the message
212
+ *  "nick": nick,//the nickname of the user that sent the message
213
+ *  "message": txt//the text of the message
214
+ * }}
215
+ * outgoingMessage - receives event notifications about outgoing
216
+ * messages. The listener will receive object with the following structure:
217
+ * {{
218
+ *  "message": txt//the text of the message
219
+ * }}
220
+ * displayNameChanged - receives event notifications about display name
221
+ * change. The listener will receive object with the following structure:
222
+ * {{
223
+ * jid: jid,//the JID of the participant that changed his display name
224
+ * displayname: displayName //the new display name
225
+ * }}
226
+ * participantJoined - receives event notifications about new participant.
227
+ * The listener will receive object with the following structure:
228
+ * {{
229
+ * jid: jid //the jid of the participant
230
+ * }}
231
+ * participantLeft - receives event notifications about the participant that
232
+ * left the room.
233
+ * The listener will receive object with the following structure:
234
+ * {{
235
+ * jid: jid //the jid of the participant
236
+ * }}
237
+ * video-conference-joined - receives event notifications about the local user
238
+ * has successfully joined the video conference.
239
+ * The listener will receive object with the following structure:
240
+ * {{
241
+ * roomName: room //the room name of the conference
242
+ * }}
243
+ * video-conference-left - receives event notifications about the local user
244
+ * has left the video conference.
245
+ * The listener will receive object with the following structure:
246
+ * {{
247
+ * roomName: room //the room name of the conference
248
+ * }}
249
+ * @param object
250
+ */
251
+JitsiMeetExternalAPI.prototype.addEventListeners = function(object) {
252
+    for(var i in object)
253
+        this.addEventListener(i, object[i]);
254
+};
255
+
256
+/**
257
+ * Adds event listeners to Meet Jitsi. Currently we support the following
258
+ * events:
259
+ * incomingMessage - receives event notifications about incoming
260
+ * messages. The listener will receive object with the following structure:
261
+ * {{
262
+ *  "from": from,//JID of the user that sent the message
263
+ *  "nick": nick,//the nickname of the user that sent the message
264
+ *  "message": txt//the text of the message
265
+ * }}
266
+ * outgoingMessage - receives event notifications about outgoing
267
+ * messages. The listener will receive object with the following structure:
268
+ * {{
269
+ *  "message": txt//the text of the message
270
+ * }}
271
+ * displayNameChanged - receives event notifications about display name
272
+ * change. The listener will receive object with the following structure:
273
+ * {{
274
+ * jid: jid,//the JID of the participant that changed his display name
275
+ * displayname: displayName //the new display name
276
+ * }}
277
+ * participantJoined - receives event notifications about new participant.
278
+ * The listener will receive object with the following structure:
279
+ * {{
280
+ * jid: jid //the jid of the participant
281
+ * }}
282
+ * participantLeft - receives event notifications about participant the that
283
+ * left the room.
284
+ * The listener will receive object with the following structure:
285
+ * {{
286
+ * jid: jid //the jid of the participant
287
+ * }}
288
+ * video-conference-joined - receives event notifications fired when the local
289
+ * user has joined the video conference.
290
+ * The listener will receive object with the following structure:
291
+ * {{
292
+ * roomName: room //the room name of the conference
293
+ * }}
294
+ * video-conference-left - receives event notifications fired when the local
295
+ * user has joined the video conference.
296
+ * The listener will receive object with the following structure:
297
+ * {{
298
+ * roomName: room //the room name of the conference
299
+ * }}
300
+ * @param event the name of the event
301
+ * @param listener the listener
302
+ */
303
+JitsiMeetExternalAPI.prototype.addEventListener = function(event, listener) {
304
+    if(!(event in events)) {
305
+        console.error("Not supported event name.");
306
+        return;
307
+    }
308
+    // We cannot remove listeners from postis that's why we are handling the
309
+    // callback that way.
310
+    if(!(event in this.eventHandlers))
311
+        this.postis.listen(events[event], function(data) {
312
+            if((event in this.eventHandlers) &&
313
+                typeof this.eventHandlers[event] === "function")
314
+                this.eventHandlers[event].call(null, data);
315
+        }.bind(this));
316
+    this.eventHandlers[event] = listener;
317
+    changeEventStatus(this.postis, event, true);
318
+};
319
+
320
+/**
321
+ * Removes event listener.
322
+ * @param event the name of the event.
323
+ */
324
+JitsiMeetExternalAPI.prototype.removeEventListener = function(event) {
325
+    if(!(event in this.eventHandlers))
326
+    {
327
+        console.error("The event " + event + " is not registered.");
328
+        return;
329
+    }
330
+    delete this.eventHandlers[event];
331
+    changeEventStatus(this.postis, event, false);
332
+};
333
+
334
+/**
335
+ * Removes event listeners.
336
+ * @param events array with the names of the events.
337
+ */
338
+JitsiMeetExternalAPI.prototype.removeEventListeners = function(events) {
339
+    var eventsArray = [];
340
+    for(var i = 0; i < events.length; i++)
341
+        this.removeEventListener(events[i]);
342
+};
343
+
344
+/**
345
+ * Removes the listeners and removes the Jitsi Meet frame.
346
+ */
347
+JitsiMeetExternalAPI.prototype.dispose = function() {
348
+    this.postis.dispose();
349
+    var frame = document.getElementById(this.frameName);
350
+    if(frame)
351
+        frame.src = 'about:blank';
352
+    var self = this;
353
+    window.setTimeout(function () {
354
+        self.iframeHolder.removeChild(self.frame);
355
+        self.iframeHolder.parentNode.removeChild(self.iframeHolder);
356
+    }, 10);
357
+};
358
+
359
+module.exports = JitsiMeetExternalAPI;

+ 2
- 1
modules/TokenData/TokenData.js Просмотреть файл

@@ -72,7 +72,8 @@ class TokenData{
72 72
 
73 73
         //External API settings
74 74
         this.externalAPISettings = {
75
-            enablePostis: true
75
+            forceEnable: true,
76
+            enabledEvents: ["video-conference-joined", "video-conference-left"]
76 77
         };
77 78
         this._decode();
78 79
         // Use JWT param as token if there is not other token set and if the

+ 44
- 3
modules/UI/UI.js Просмотреть файл

@@ -40,6 +40,8 @@ let sharedVideoManager;
40 40
 
41 41
 let followMeHandler;
42 42
 
43
+let deviceErrorDialog;
44
+
43 45
 const TrackErrors = JitsiMeetJS.errors.track;
44 46
 
45 47
 const JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP = {
@@ -256,7 +258,25 @@ UI.changeDisplayName = function (id, displayName) {
256 258
 };
257 259
 
258 260
 /**
259
- * Intitialize conference UI.
261
+ * Sets the "raised hand" status for a participant.
262
+ */
263
+UI.setRaisedHandStatus = (participant, raisedHandStatus) => {
264
+    VideoLayout.setRaisedHandStatus(participant.getId(), raisedHandStatus);
265
+    if (raisedHandStatus) {
266
+        messageHandler.notify(participant.getDisplayName(), 'notify.somebody',
267
+                          'connected', 'notify.raisedHand');
268
+    }
269
+};
270
+
271
+/**
272
+ * Sets the local "raised hand" status.
273
+ */
274
+UI.setLocalRaisedHandStatus = (raisedHandStatus) => {
275
+    VideoLayout.setRaisedHandStatus(APP.conference.localId, raisedHandStatus);
276
+};
277
+
278
+/**
279
+ * Initialize conference UI.
260 280
  */
261 281
 UI.initConference = function () {
262 282
     let id = APP.conference.localId;
@@ -1268,7 +1288,11 @@ UI.showDeviceErrorDialog = function (micError, cameraError) {
1268 1288
 
1269 1289
     message = `${message}${doNotShowWarningAgainSection}`;
1270 1290
 
1271
-    messageHandler.openDialog(
1291
+    // To make sure we don't have multiple error dialogs open at the same time,
1292
+    // we will just close the previous one if we are going to show a new one.
1293
+    deviceErrorDialog && deviceErrorDialog.close();
1294
+
1295
+    deviceErrorDialog = messageHandler.openDialog(
1272 1296
         titleMsg,
1273 1297
         message,
1274 1298
         false,
@@ -1284,6 +1308,12 @@ UI.showDeviceErrorDialog = function (micError, cameraError) {
1284 1308
                         input.prop("checked");
1285 1309
                 }
1286 1310
             }
1311
+        },
1312
+        null,
1313
+        function () {
1314
+            // Reset dialog reference to null to avoid memory leaks when
1315
+            // user closed the dialog manually.
1316
+            deviceErrorDialog = null;
1287 1317
         }
1288 1318
     );
1289 1319
 
@@ -1403,10 +1433,21 @@ UI.hideUserMediaPermissionsGuidanceOverlay = function () {
1403 1433
 };
1404 1434
 
1405 1435
 /**
1406
- * Shows or hides the keyboard shortcuts panel.'
1436
+ * Shows or hides the keyboard shortcuts panel, depending on the current state.'
1407 1437
  */
1408 1438
 UI.toggleKeyboardShortcutsPanel = function() {
1409 1439
     $('#keyboard-shortcuts').toggle();
1410 1440
 };
1411 1441
 
1442
+/**
1443
+ * Shows or hides the keyboard shortcuts panel.'
1444
+ */
1445
+UI.showKeyboardShortcutsPanel = function(show) {
1446
+    if (show) {
1447
+        $('#keyboard-shortcuts').show();
1448
+    } else {
1449
+        $('#keyboard-shortcuts').hide();
1450
+    }
1451
+};
1452
+
1412 1453
 module.exports = UI;

+ 15
- 8
modules/UI/util/MessageHandler.js Просмотреть файл

@@ -113,9 +113,10 @@ var messageHandler = {
113 113
      * @param submitFunction function to be called on submit
114 114
      * @param loadedFunction function to be called after the prompt is fully
115 115
      *        loaded
116
+     * @param closeFunction function to be called on dialog close
116 117
      */
117 118
     openDialog: function (titleString, msgString, persistent, buttons,
118
-                              submitFunction, loadedFunction) {
119
+                              submitFunction, loadedFunction, closeFunction) {
119 120
         if (!popupEnabled)
120 121
             return;
121 122
 
@@ -125,11 +126,14 @@ var messageHandler = {
125 126
             buttons: buttons,
126 127
             defaultButton: 1,
127 128
             loaded: loadedFunction,
128
-            submit: submitFunction
129
+            submit: submitFunction,
130
+            close: closeFunction
129 131
         };
132
+
130 133
         if (persistent) {
131 134
             args.closeText = '';
132 135
         }
136
+        
133 137
         return new Impromptu(msgString, args);
134 138
     },
135 139
 
@@ -215,16 +219,19 @@ var messageHandler = {
215 219
     },
216 220
 
217 221
     /**
218
-     * Displayes notification.
219
-     * @param displayName display name of the participant that is associated with the notification.
220
-     * @param displayNameKey the key from the language file for the display name.
222
+     * Displays a notification.
223
+     * @param displayName the display name of the participant that is
224
+     * associated with the notification.
225
+     * @param displayNameKey the key from the language file for the display
226
+     * name. Only used if displayName i not provided.
221 227
      * @param cls css class for the notification
222
-     * @param messageKey the key from the language file for the text of the message.
228
+     * @param messageKey the key from the language file for the text of the
229
+     * message.
223 230
      * @param messageArguments object with the arguments for the message.
224 231
      * @param options object with language options.
225 232
      */
226
-    notify: function(displayName, displayNameKey,
227
-                         cls, messageKey, messageArguments, options) {
233
+    notify: function(displayName, displayNameKey, cls, messageKey,
234
+                     messageArguments, options) {
228 235
 
229 236
         if(!notificationsEnabled)
230 237
             return;

+ 51
- 19
modules/UI/videolayout/SmallVideo.js Просмотреть файл

@@ -422,37 +422,69 @@ SmallVideo.prototype.avatarChanged = function (avatarUrl) {
422 422
 };
423 423
 
424 424
 /**
425
- * Updates the Indicator for dominant speaker.
426
- *
427
- * @param isSpeaker indicates the current indicator state
425
+ * Shows or hides the dominant speaker indicator.
426
+ * @param show whether to show or hide.
428 427
  */
429
-SmallVideo.prototype.updateDominantSpeakerIndicator = function (isSpeaker) {
430
-
428
+SmallVideo.prototype.showDominantSpeakerIndicator = function (show) {
431 429
     if (!this.container) {
432 430
         console.warn( "Unable to set dominant speaker indicator - "
433 431
             + this.videoSpanId + " does not exist");
434 432
         return;
435 433
     }
436 434
 
437
-    var indicatorSpan
438
-        = $('#' + this.videoSpanId + '>span.dominantspeakerindicator');
439
-
440
-    // If we do not have an indicator for this video.
441
-    if (indicatorSpan.length <= 0) {
442
-        indicatorSpan = document.createElement('span');
435
+    var indicatorSpanId = "dominantspeakerindicator";
436
+    var indicatorSpan = this.getIndicatorSpan(indicatorSpanId);
443 437
 
444
-        indicatorSpan.innerHTML
445
-            = "<i id='speakerindicatoricon' class='fa fa-bullhorn'></i>";
446
-        indicatorSpan.className = 'dominantspeakerindicator';
438
+    indicatorSpan.innerHTML
439
+        = "<i id='indicatoricon' class='fa fa-bullhorn'></i>";
440
+    // adds a tooltip
441
+    UIUtil.setTooltip(indicatorSpan, "speaker", "left");
442
+    APP.translation.translateElement($(indicatorSpan));
447 443
 
448
-        $('#' + this.videoSpanId)[0].appendChild(indicatorSpan);
444
+    $(indicatorSpan).css("visibility", show ? "visible" : "hidden");
445
+};
449 446
 
450
-        // adds a tooltip
451
-        UIUtil.setTooltip(indicatorSpan, "speaker", "left");
452
-        APP.translation.translateElement($(indicatorSpan));
447
+/**
448
+ * Shows or hides the raised hand indicator.
449
+ * @param show whether to show or hide.
450
+ */
451
+SmallVideo.prototype.showRaisedHandIndicator = function (show) {
452
+    if (!this.container) {
453
+        console.warn( "Unable to raised hand indication - "
454
+            + this.videoSpanId + " does not exist");
455
+        return;
453 456
     }
454 457
 
455
-    $(indicatorSpan).css("visibility", isSpeaker ? "visible" : "hidden");
458
+    var indicatorSpanId = "raisehandindicator";
459
+    var indicatorSpan = this.getIndicatorSpan(indicatorSpanId);
460
+
461
+    indicatorSpan.style.background = "#D6D61E";
462
+    indicatorSpan.innerHTML
463
+        = "<i id='indicatoricon' class='fa fa-hand-paper-o'></i>";
464
+
465
+    // adds a tooltip
466
+    UIUtil.setTooltip(indicatorSpan, "raisedHand", "left");
467
+    APP.translation.translateElement($(indicatorSpan));
468
+
469
+    $(indicatorSpan).css("visibility", show ? "visible" : "hidden");
470
+};
471
+
472
+/**
473
+ * Gets (creating if necessary) the "indicator" span for this SmallVideo
474
+  identified by an ID.
475
+ */
476
+SmallVideo.prototype.getIndicatorSpan = function(id) {
477
+    var indicatorSpan;
478
+    var spans = $(`#${this.videoSpanId}>[id=${id}`);
479
+    if (spans.length <= 0) {
480
+        indicatorSpan = document.createElement('span');
481
+        indicatorSpan.id = id;
482
+        indicatorSpan.className = "indicator";
483
+        $('#' + this.videoSpanId)[0].appendChild(indicatorSpan);
484
+    } else {
485
+        indicatorSpan = spans[0];
486
+    }
487
+    return indicatorSpan;
456 488
 };
457 489
 
458 490
 export default SmallVideo;

+ 17
- 5
modules/UI/videolayout/VideoLayout.js Просмотреть файл

@@ -562,6 +562,18 @@ var VideoLayout = {
562 562
         }
563 563
     },
564 564
 
565
+    /**
566
+     * Sets the "raised hand" status for a participant identified by 'id'.
567
+     */
568
+    setRaisedHandStatus(id, raisedHandStatus) {
569
+        var video
570
+            = APP.conference.isLocalId(id)
571
+                ? localVideoThumbnail : remoteVideos[id];
572
+        if (video) {
573
+            video.showRaisedHandIndicator(raisedHandStatus);
574
+        }
575
+    },
576
+
565 577
     /**
566 578
      * On dominant speaker changed event.
567 579
      */
@@ -576,10 +588,10 @@ var VideoLayout = {
576 588
         if (APP.conference.isLocalId(id)) {
577 589
             if(oldSpeakerRemoteVideo)
578 590
             {
579
-                oldSpeakerRemoteVideo.updateDominantSpeakerIndicator(false);
580
-                localVideoThumbnail.updateDominantSpeakerIndicator(true);
591
+                oldSpeakerRemoteVideo.showDominantSpeakerIndicator(false);
581 592
                 currentDominantSpeaker = null;
582 593
             }
594
+            localVideoThumbnail.showDominantSpeakerIndicator(true);
583 595
             return;
584 596
         }
585 597
 
@@ -589,12 +601,12 @@ var VideoLayout = {
589 601
         }
590 602
 
591 603
         // Update the current dominant speaker.
592
-        remoteVideo.updateDominantSpeakerIndicator(true);
593
-        localVideoThumbnail.updateDominantSpeakerIndicator(false);
604
+        remoteVideo.showDominantSpeakerIndicator(true);
605
+        localVideoThumbnail.showDominantSpeakerIndicator(false);
594 606
 
595 607
         // let's remove the indications from the remote video if any
596 608
         if (oldSpeakerRemoteVideo) {
597
-            oldSpeakerRemoteVideo.updateDominantSpeakerIndicator(false);
609
+            oldSpeakerRemoteVideo.showDominantSpeakerIndicator(false);
598 610
         }
599 611
         currentDominantSpeaker = id;
600 612
 

+ 6
- 4
modules/devices/mediaDeviceHelper.js Просмотреть файл

@@ -197,15 +197,17 @@ export default {
197 197
                         micDeviceId: micDeviceId
198 198
                     })
199 199
                     // If we fail to do this, try to create them separately.
200
-                    .catch(() => Promise.all(
201
-                        [createAudioTrack(false), createVideoTrack(false)]))
202
-                    .then((audioTracks, videoTracks) => {
200
+                    .catch(() => Promise.all([
201
+                        createAudioTrack(false).then(([stream]) => stream),
202
+                        createVideoTrack(false).then(([stream]) => stream)
203
+                    ]))
204
+                    .then(tracks => {
203 205
                         if (audioTrackError || videoTrackError) {
204 206
                             APP.UI.showDeviceErrorDialog(
205 207
                                 audioTrackError, videoTrackError);
206 208
                         }
207 209
 
208
-                        return (audioTracks || []).concat(videoTracks || []);
210
+                        return tracks.filter(t => typeof t !== 'undefined');
209 211
                     });
210 212
         } else if (videoRequested && !audioRequested) {
211 213
             return createVideoTrack();

+ 20
- 7
modules/keyboardshortcut/keyboardshortcut.js Просмотреть файл

@@ -3,13 +3,10 @@
3 3
 var shortcuts = {};
4 4
 function initShortcutHandlers() {
5 5
     shortcuts = {
6
-        191: {
7
-            character: "/",
8
-            function: function(e) {
9
-                // Only trigger on "?", not on "/".
10
-                if (e.shiftKey) {
11
-                    APP.UI.toggleKeyboardShortcutsPanel();
12
-                }
6
+        27: {
7
+            character: "Esc",
8
+            function: function() {
9
+                APP.UI.showKeyboardShortcutsPanel(false);
13 10
             }
14 11
         },
15 12
         67: {
@@ -40,6 +37,13 @@ function initShortcutHandlers() {
40 37
                 APP.conference.toggleAudioMuted();
41 38
             }
42 39
         },
40
+        82: {
41
+            character: "R",
42
+            function: function() {
43
+                APP.conference.maybeToggleRaisedHand();
44
+            }
45
+
46
+        },
43 47
         84: {
44 48
             character: "T",
45 49
             function: function() {
@@ -52,6 +56,15 @@ function initShortcutHandlers() {
52 56
             function: function() {
53 57
                 APP.conference.toggleVideoMuted();
54 58
             }
59
+        },
60
+        191: {
61
+            character: "/",
62
+            function: function(e) {
63
+                // Only trigger on "?", not on "/".
64
+                if (e.shiftKey) {
65
+                    APP.UI.toggleKeyboardShortcutsPanel();
66
+                }
67
+            }
55 68
         }
56 69
     };
57 70
 }

Загрузка…
Отмена
Сохранить