Browse Source

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

j8
tsareg 9 years ago
parent
commit
f03b228eea

+ 1
- 3
.gitignore View File

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

+ 11
- 2
Makefile View File

8
 BROWSERIFY_FLAGS = -d
8
 BROWSERIFY_FLAGS = -d
9
 OUTPUT_DIR = .
9
 OUTPUT_DIR = .
10
 LIBJITSIMEET_DIR = node_modules/lib-jitsi-meet/
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
 update-deps:
15
 update-deps:
15
 	$(NPM) install
16
 	$(NPM) install
17
 compile:
18
 compile:
18
 	$(BROWSERIFY) $(BROWSERIFY_FLAGS) -e app.js -s APP | $(EXORCIST) $(OUTPUT_DIR)/app.bundle.js.map > $(OUTPUT_DIR)/app.bundle.js
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
 clean:
24
 clean:
21
-	rm -f $(OUTPUT_DIR)/app.bundle.*
25
+	rm -f $(OUTPUT_DIR)/app.bundle.* $(OUTPUT_DIR)/external_api.*
22
 
26
 
23
 deploy: deploy-init deploy-appbundle deploy-lib-jitsi-meet deploy-css deploy-local
27
 deploy: deploy-init deploy-appbundle deploy-lib-jitsi-meet deploy-css deploy-local
24
 
28
 
28
 deploy-appbundle:
32
 deploy-appbundle:
29
 	cp $(OUTPUT_DIR)/app.bundle.min.js $(OUTPUT_DIR)/app.bundle.min.map \
33
 	cp $(OUTPUT_DIR)/app.bundle.min.js $(OUTPUT_DIR)/app.bundle.min.map \
30
 	$(OUTPUT_DIR)/app.bundle.js $(OUTPUT_DIR)/app.bundle.js.map \
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
 	$(DEPLOY_DIR)
37
 	$(DEPLOY_DIR)
32
 
38
 
33
 deploy-lib-jitsi-meet:
39
 deploy-lib-jitsi-meet:
46
 uglify:
52
 uglify:
47
 	$(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
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
 source-package:
59
 source-package:
51
 	mkdir -p source_package/jitsi-meet/css && \
60
 	mkdir -p source_package/jitsi-meet/css && \

+ 1
- 4
app.js View File

116
             APP.keyboardshortcut.init();
116
             APP.keyboardshortcut.init();
117
         }).catch(function (err) {
117
         }).catch(function (err) {
118
             APP.UI.hideRingOverLay();
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
             console.error(err);
120
             console.error(err);
124
         });
121
         });
125
     }
122
     }

+ 52
- 11
conference.js View File

207
 function disconnectAndShowFeedback(requestFeedback) {
207
 function disconnectAndShowFeedback(requestFeedback) {
208
     APP.UI.hideRingOverLay();
208
     APP.UI.hideRingOverLay();
209
     connection.disconnect();
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
     if (requestFeedback) {
211
     if (requestFeedback) {
215
         return APP.UI.requestFeedback();
212
         return APP.UI.requestFeedback();
216
     } else {
213
     } else {
456
     videoMuted: false,
453
     videoMuted: false,
457
     isSharingScreen: false,
454
     isSharingScreen: false,
458
     isDesktopSharingEnabled: false,
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
      * Open new connection and join to the conference.
465
      * Open new connection and join to the conference.
461
      * @param {object} options
466
      * @param {object} options
496
                 this._createRoom(tracks);
501
                 this._createRoom(tracks);
497
                 this.isDesktopSharingEnabled =
502
                 this.isDesktopSharingEnabled =
498
                     JitsiMeetJS.isDesktopSharingEnabled();
503
                     JitsiMeetJS.isDesktopSharingEnabled();
499
-                if(this.isDesktopSharingEnabled)
500
-                    APP.API.addPostisMessageListener('toggle-share-screen',
501
-                        () => this.toggleScreenSharing());
502
 
504
 
503
                 // if user didn't give access to mic or camera or doesn't have
505
                 // if user didn't give access to mic or camera or doesn't have
504
                 // them at all, we disable corresponding toolbar buttons
506
                 // them at all, we disable corresponding toolbar buttons
939
         // add local streams when joined to the conference
941
         // add local streams when joined to the conference
940
         room.on(ConferenceEvents.CONFERENCE_JOINED, () => {
942
         room.on(ConferenceEvents.CONFERENCE_JOINED, () => {
941
             APP.UI.mucJoined();
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
         room.on(
947
         room.on(
1049
             APP.UI.handleLastNEndpoints(ids, enteringIds);
1048
             APP.UI.handleLastNEndpoints(ids, enteringIds);
1050
         });
1049
         });
1051
         room.on(ConferenceEvents.DOMINANT_SPEAKER_CHANGED, (id) => {
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
             APP.UI.markDominantSpeaker(id);
1061
             APP.UI.markDominantSpeaker(id);
1053
         });
1062
         });
1054
 
1063
 
1071
             APP.UI.changeDisplayName(id, displayName);
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
         room.on(ConferenceEvents.RECORDER_STATE_CHANGED, (status, error) => {
1090
         room.on(ConferenceEvents.RECORDER_STATE_CHANGED, (status, error) => {
1075
             console.log("Received recorder status change: ", status, error);
1091
             console.log("Received recorder status change: ", status, error);
1076
             APP.UI.updateRecordingState(status);
1092
             APP.UI.updateRecordingState(status);
1471
                 mediaDeviceHelper.setCurrentMediaDevices(devices);
1487
                 mediaDeviceHelper.setCurrentMediaDevices(devices);
1472
                 APP.UI.onAvailableDevicesChanged(devices);
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 View File

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

+ 1
- 1
debian/control View File

3
 Priority: extra
3
 Priority: extra
4
 Maintainer: Jitsi Team <dev@jitsi.org>
4
 Maintainer: Jitsi Team <dev@jitsi.org>
5
 Uploaders: Emil Ivov <emcho@jitsi.org>, Damian Minkov <damencho@jitsi.org>
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
 Standards-Version: 3.9.6
7
 Standards-Version: 3.9.6
8
 Homepage: https://jitsi.org/meet
8
 Homepage: https://jitsi.org/meet
9
 
9
 

+ 28
- 9
doc/api.md View File

20
     var height = 700;
20
     var height = 700;
21
     var api = new JitsiMeetExternalAPI(domain, room, width, height);
21
     var api = new JitsiMeetExternalAPI(domain, room, width, height);
22
 </script>
22
 </script>
23
-``` 
23
+```
24
 You can paste that lines in your html code where you want to be placed the Jitsi Meet conference
24
 You can paste that lines in your html code where you want to be placed the Jitsi Meet conference
25
 or you can specify the parent HTML element for the Jitsi Meet conference in the JitsiMeetExternalAPI
25
 or you can specify the parent HTML element for the Jitsi Meet conference in the JitsiMeetExternalAPI
26
 constructor.
26
 constructor.
27
 ```javascript
27
 ```javascript
28
     var api = new JitsiMeetExternalAPI(domain, room, width, height, htmlElement);
28
     var api = new JitsiMeetExternalAPI(domain, room, width, height, htmlElement);
29
-``` 
29
+```
30
 If you don't specify room the user will enter in new conference with random room name.
30
 If you don't specify room the user will enter in new conference with random room name.
31
 
31
 
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:
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
     var configOverwrite = {enableSimulcast: false};
34
     var configOverwrite = {enableSimulcast: false};
35
     var interfaceConfigOverwrite = {filmStripOnly: true};
35
     var interfaceConfigOverwrite = {filmStripOnly: true};
36
     var api = new JitsiMeetExternalAPI(domain, room, width, height, htmlElement, true, configOverwrite, interfaceConfigOverwrite);
36
     var api = new JitsiMeetExternalAPI(domain, room, width, height, htmlElement, true, configOverwrite, interfaceConfigOverwrite);
37
-``` 
37
+```
38
 
38
 
39
 Controlling embedded Jitsi Meet Conference
39
 Controlling embedded Jitsi Meet Conference
40
 =========
40
 =========
41
 
41
 
42
 You can control the embedded Jitsi Meet conference using the JitsiMeetExternalAPI object.
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
 api.executeCommand(command, arguments)
46
 api.executeCommand(command, arguments)
47
 ```
47
 ```
48
 The ```command``` parameter is String object with the name of the command.
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
 If no arguments are required by the command this parameter can be omitted or you can pass empty array.
50
 If no arguments are required by the command this parameter can be omitted or you can pass empty array.
51
 Currently we support the following commands:
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
 the new display name to be set
55
 the new display name to be set
56
 ```
56
 ```
57
 api.executeCommand('displayName', ['New Nickname']);
57
 api.executeCommand('displayName', ['New Nickname']);
77
 api.executeCommand('toggleContactList', [])
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
 api.executeCommands(commands)
87
 api.executeCommands(commands)
83
 ```
88
 ```
136
 jid: jid //the jid of the participant
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
 You can also add multiple event listeners by using ```addEventListeners```.
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
 have keys with the names of the events and values the listeners of the events.
161
 have keys with the names of the events and values the listeners of the events.
143
 
162
 
144
 ```
163
 ```
173
 api.dispose()
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 View File

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

+ 0
- 378
external_api.js View File

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 View File

241
             <div class="arrow-up"></div>
241
             <div class="arrow-up"></div>
242
             <input type="text" id="setDisplayName" data-i18n="[placeholder]settings.name" placeholder="Name">
242
             <input type="text" id="setDisplayName" data-i18n="[placeholder]settings.name" placeholder="Name">
243
             <input type="text" id="setEmail" placeholder="E-Mail">
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
             <select id="languages_selectbox"></select>
245
             <select id="languages_selectbox"></select>
246
             <div id = "startMutedOptions">
246
             <div id = "startMutedOptions">
247
                 <label class = "startMutedLabel">
247
                 <label class = "startMutedLabel">

+ 3
- 1
lang/main.json View File

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

+ 107
- 166
modules/API/API.js View File

1
-/* global APP */
1
+/* global APP, getConfigParamsFromUrl */
2
 /**
2
 /**
3
  * Implements API class that communicates with external api class
3
  * Implements API class that communicates with external api class
4
  * and provides interface to access Jitsi Meet features by external
4
  * and provides interface to access Jitsi Meet features by external
5
  * applications that embed Jitsi Meet
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
  * List of the available commands.
11
  * List of the available commands.
20
  */
20
  */
21
 let commands = {};
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
 function initCommands() {
45
 function initCommands() {
45
     commands = {
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
  * Maps the supported events and their status
62
  * Maps the supported events and their status
58
  * (true it the event is enabled and false if it is disabled)
63
  * (true it the event is enabled and false if it is disabled)
59
  * @type {{
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
 const events = {
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
  * Sends message to the external application.
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
  * @returns {boolean}
97
  * @returns {boolean}
154
  */
98
  */
155
 function isEnabled () {
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
  * @param object data associated with the event
116
  * @param object data associated with the event
174
  */
117
  */
175
 function triggerEvent (name, object) {
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
      * It also sends a message to the external application that APIConnector
145
      * It also sends a message to the external application that APIConnector
191
      * is initialized.
146
      * is initialized.
192
      * @param options {object}
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
             return;
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
      * @param {string} body message body
173
      * @param {string} body message body
225
      */
174
      */
226
     notifySendingChatMessage (body) {
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
         }
190
         }
263
 
191
 
264
         triggerEvent(
192
         triggerEvent(
265
-            "incomingMessage",
193
+            "incoming-message",
266
             {"from": id, "nick": nick, "message": body, "stamp": ts}
194
             {"from": id, "nick": nick, "message": body, "stamp": ts}
267
         );
195
         );
268
     },
196
     },
273
      * @param {string} id user id
201
      * @param {string} id user id
274
      */
202
      */
275
     notifyUserJoined (id) {
203
     notifyUserJoined (id) {
276
-        triggerEvent("participantJoined", {id});
204
+        triggerEvent("participant-joined", {id});
277
     },
205
     },
278
 
206
 
279
     /**
207
     /**
282
      * @param {string} id user id
210
      * @param {string} id user id
283
      */
211
      */
284
     notifyUserLeft (id) {
212
     notifyUserLeft (id) {
285
-        triggerEvent("participantLeft", {id});
213
+        triggerEvent("participant-left", {id});
286
     },
214
     },
287
 
215
 
288
     /**
216
     /**
292
      * @param {string} displayName user nickname
220
      * @param {string} displayName user nickname
293
      */
221
      */
294
     notifyDisplayNameChanged (id, displayName) {
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
      * Removes the listeners.
247
      * Removes the listeners.
300
      */
248
      */
301
     dispose: function () {
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 View File

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 View File

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

+ 44
- 3
modules/UI/UI.js View File

40
 
40
 
41
 let followMeHandler;
41
 let followMeHandler;
42
 
42
 
43
+let deviceErrorDialog;
44
+
43
 const TrackErrors = JitsiMeetJS.errors.track;
45
 const TrackErrors = JitsiMeetJS.errors.track;
44
 
46
 
45
 const JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP = {
47
 const JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP = {
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
 UI.initConference = function () {
281
 UI.initConference = function () {
262
     let id = APP.conference.localId;
282
     let id = APP.conference.localId;
1268
 
1288
 
1269
     message = `${message}${doNotShowWarningAgainSection}`;
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
         titleMsg,
1296
         titleMsg,
1273
         message,
1297
         message,
1274
         false,
1298
         false,
1284
                         input.prop("checked");
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
 };
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
 UI.toggleKeyboardShortcutsPanel = function() {
1438
 UI.toggleKeyboardShortcutsPanel = function() {
1409
     $('#keyboard-shortcuts').toggle();
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
 module.exports = UI;
1453
 module.exports = UI;

+ 15
- 8
modules/UI/util/MessageHandler.js View File

113
      * @param submitFunction function to be called on submit
113
      * @param submitFunction function to be called on submit
114
      * @param loadedFunction function to be called after the prompt is fully
114
      * @param loadedFunction function to be called after the prompt is fully
115
      *        loaded
115
      *        loaded
116
+     * @param closeFunction function to be called on dialog close
116
      */
117
      */
117
     openDialog: function (titleString, msgString, persistent, buttons,
118
     openDialog: function (titleString, msgString, persistent, buttons,
118
-                              submitFunction, loadedFunction) {
119
+                              submitFunction, loadedFunction, closeFunction) {
119
         if (!popupEnabled)
120
         if (!popupEnabled)
120
             return;
121
             return;
121
 
122
 
125
             buttons: buttons,
126
             buttons: buttons,
126
             defaultButton: 1,
127
             defaultButton: 1,
127
             loaded: loadedFunction,
128
             loaded: loadedFunction,
128
-            submit: submitFunction
129
+            submit: submitFunction,
130
+            close: closeFunction
129
         };
131
         };
132
+
130
         if (persistent) {
133
         if (persistent) {
131
             args.closeText = '';
134
             args.closeText = '';
132
         }
135
         }
136
+        
133
         return new Impromptu(msgString, args);
137
         return new Impromptu(msgString, args);
134
     },
138
     },
135
 
139
 
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
      * @param cls css class for the notification
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
      * @param messageArguments object with the arguments for the message.
230
      * @param messageArguments object with the arguments for the message.
224
      * @param options object with language options.
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
         if(!notificationsEnabled)
236
         if(!notificationsEnabled)
230
             return;
237
             return;

+ 51
- 19
modules/UI/videolayout/SmallVideo.js View File

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
     if (!this.container) {
429
     if (!this.container) {
432
         console.warn( "Unable to set dominant speaker indicator - "
430
         console.warn( "Unable to set dominant speaker indicator - "
433
             + this.videoSpanId + " does not exist");
431
             + this.videoSpanId + " does not exist");
434
         return;
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
 export default SmallVideo;
490
 export default SmallVideo;

+ 17
- 5
modules/UI/videolayout/VideoLayout.js View File

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
      * On dominant speaker changed event.
578
      * On dominant speaker changed event.
567
      */
579
      */
576
         if (APP.conference.isLocalId(id)) {
588
         if (APP.conference.isLocalId(id)) {
577
             if(oldSpeakerRemoteVideo)
589
             if(oldSpeakerRemoteVideo)
578
             {
590
             {
579
-                oldSpeakerRemoteVideo.updateDominantSpeakerIndicator(false);
580
-                localVideoThumbnail.updateDominantSpeakerIndicator(true);
591
+                oldSpeakerRemoteVideo.showDominantSpeakerIndicator(false);
581
                 currentDominantSpeaker = null;
592
                 currentDominantSpeaker = null;
582
             }
593
             }
594
+            localVideoThumbnail.showDominantSpeakerIndicator(true);
583
             return;
595
             return;
584
         }
596
         }
585
 
597
 
589
         }
601
         }
590
 
602
 
591
         // Update the current dominant speaker.
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
         // let's remove the indications from the remote video if any
607
         // let's remove the indications from the remote video if any
596
         if (oldSpeakerRemoteVideo) {
608
         if (oldSpeakerRemoteVideo) {
597
-            oldSpeakerRemoteVideo.updateDominantSpeakerIndicator(false);
609
+            oldSpeakerRemoteVideo.showDominantSpeakerIndicator(false);
598
         }
610
         }
599
         currentDominantSpeaker = id;
611
         currentDominantSpeaker = id;
600
 
612
 

+ 6
- 4
modules/devices/mediaDeviceHelper.js View File

197
                         micDeviceId: micDeviceId
197
                         micDeviceId: micDeviceId
198
                     })
198
                     })
199
                     // If we fail to do this, try to create them separately.
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
                         if (audioTrackError || videoTrackError) {
205
                         if (audioTrackError || videoTrackError) {
204
                             APP.UI.showDeviceErrorDialog(
206
                             APP.UI.showDeviceErrorDialog(
205
                                 audioTrackError, videoTrackError);
207
                                 audioTrackError, videoTrackError);
206
                         }
208
                         }
207
 
209
 
208
-                        return (audioTracks || []).concat(videoTracks || []);
210
+                        return tracks.filter(t => typeof t !== 'undefined');
209
                     });
211
                     });
210
         } else if (videoRequested && !audioRequested) {
212
         } else if (videoRequested && !audioRequested) {
211
             return createVideoTrack();
213
             return createVideoTrack();

+ 20
- 7
modules/keyboardshortcut/keyboardshortcut.js View File

3
 var shortcuts = {};
3
 var shortcuts = {};
4
 function initShortcutHandlers() {
4
 function initShortcutHandlers() {
5
     shortcuts = {
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
         67: {
12
         67: {
40
                 APP.conference.toggleAudioMuted();
37
                 APP.conference.toggleAudioMuted();
41
             }
38
             }
42
         },
39
         },
40
+        82: {
41
+            character: "R",
42
+            function: function() {
43
+                APP.conference.maybeToggleRaisedHand();
44
+            }
45
+
46
+        },
43
         84: {
47
         84: {
44
             character: "T",
48
             character: "T",
45
             function: function() {
49
             function: function() {
52
             function: function() {
56
             function: function() {
53
                 APP.conference.toggleVideoMuted();
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
 }

Loading…
Cancel
Save