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

This commit is contained in:
tsareg 2016-06-24 13:02:58 +03:00
commit f03b228eea
20 changed files with 727 additions and 626 deletions

4
.gitignore vendored
View File

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

View File

@ -8,8 +8,9 @@ DEPLOY_DIR = libs
BROWSERIFY_FLAGS = -d
OUTPUT_DIR = .
LIBJITSIMEET_DIR = node_modules/lib-jitsi-meet/
IFRAME_API_DIR = ./modules/API/external
all: update-deps compile uglify deploy clean
all: update-deps compile compile-iframe-api uglify uglify-iframe-api deploy clean
update-deps:
$(NPM) install
@ -17,8 +18,11 @@ update-deps:
compile:
$(BROWSERIFY) $(BROWSERIFY_FLAGS) -e app.js -s APP | $(EXORCIST) $(OUTPUT_DIR)/app.bundle.js.map > $(OUTPUT_DIR)/app.bundle.js
compile-iframe-api:
$(BROWSERIFY) $(BROWSERIFY_FLAGS) -e $(IFRAME_API_DIR)/external_api.js -s JitsiMeetExternalAPI | $(EXORCIST) $(OUTPUT_DIR)/external_api.js.map > $(OUTPUT_DIR)/external_api.js
clean:
rm -f $(OUTPUT_DIR)/app.bundle.*
rm -f $(OUTPUT_DIR)/app.bundle.* $(OUTPUT_DIR)/external_api.*
deploy: deploy-init deploy-appbundle deploy-lib-jitsi-meet deploy-css deploy-local
@ -28,6 +32,8 @@ deploy-init:
deploy-appbundle:
cp $(OUTPUT_DIR)/app.bundle.min.js $(OUTPUT_DIR)/app.bundle.min.map \
$(OUTPUT_DIR)/app.bundle.js $(OUTPUT_DIR)/app.bundle.js.map \
$(OUTPUT_DIR)/external_api.js.map $(OUTPUT_DIR)/external_api.js \
$(OUTPUT_DIR)/external_api.min.map $(OUTPUT_DIR)/external_api.min.js \
$(DEPLOY_DIR)
deploy-lib-jitsi-meet:
@ -46,6 +52,9 @@ deploy-local:
uglify:
$(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
uglify-iframe-api:
$(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
source-package:
mkdir -p source_package/jitsi-meet/css && \

5
app.js
View File

@ -116,10 +116,7 @@ function init() {
APP.keyboardshortcut.init();
}).catch(function (err) {
APP.UI.hideRingOverLay();
APP.API.sendPostisMessage({
method: 'video-conference-left',
params: {roomName: APP.conference.roomName}
});
APP.API.notifyConferenceLeft(APP.conference.roomName);
console.error(err);
});
}

View File

@ -207,10 +207,7 @@ function maybeRedirectToWelcomePage() {
function disconnectAndShowFeedback(requestFeedback) {
APP.UI.hideRingOverLay();
connection.disconnect();
APP.API.sendPostisMessage({
method: 'video-conference-left',
params: {roomName: APP.conference.roomName}
});
APP.API.notifyConferenceLeft(APP.conference.roomName);
if (requestFeedback) {
return APP.UI.requestFeedback();
} else {
@ -456,6 +453,14 @@ export default {
videoMuted: false,
isSharingScreen: false,
isDesktopSharingEnabled: false,
/*
* Whether the local "raisedHand" flag is on.
*/
isHandRaised: false,
/*
* Whether the local participant is the dominant speaker in the conference.
*/
isDominantSpeaker: false,
/**
* Open new connection and join to the conference.
* @param {object} options
@ -496,9 +501,6 @@ export default {
this._createRoom(tracks);
this.isDesktopSharingEnabled =
JitsiMeetJS.isDesktopSharingEnabled();
if(this.isDesktopSharingEnabled)
APP.API.addPostisMessageListener('toggle-share-screen',
() => this.toggleScreenSharing());
// if user didn't give access to mic or camera or doesn't have
// them at all, we disable corresponding toolbar buttons
@ -939,10 +941,7 @@ export default {
// add local streams when joined to the conference
room.on(ConferenceEvents.CONFERENCE_JOINED, () => {
APP.UI.mucJoined();
APP.API.sendPostisMessage({
method: 'video-conference-joined',
params: {roomName: APP.conference.roomName}
});
APP.API.notifyConferenceJoined(APP.conference.roomName);
});
room.on(
@ -1049,6 +1048,16 @@ export default {
APP.UI.handleLastNEndpoints(ids, enteringIds);
});
room.on(ConferenceEvents.DOMINANT_SPEAKER_CHANGED, (id) => {
if (this.isLocalId(id)) {
this.isDominantSpeaker = true;
this.setRaisedHand(false);
} else {
this.isDominantSpeaker = false;
var participant = room.getParticipantById(id);
if (participant) {
APP.UI.setRaisedHandStatus(participant, false);
}
}
APP.UI.markDominantSpeaker(id);
});
@ -1071,6 +1080,13 @@ export default {
APP.UI.changeDisplayName(id, displayName);
});
room.on(ConferenceEvents.PARTICIPANT_PROPERTY_CHANGED,
(participant, name, oldValue, newValue) => {
if (name === "raisedHand") {
APP.UI.setRaisedHandStatus(participant, newValue);
}
});
room.on(ConferenceEvents.RECORDER_STATE_CHANGED, (status, error) => {
console.log("Received recorder status change: ", status, error);
APP.UI.updateRecordingState(status);
@ -1471,5 +1487,30 @@ export default {
mediaDeviceHelper.setCurrentMediaDevices(devices);
APP.UI.onAvailableDevicesChanged(devices);
});
},
/**
* Toggles the local "raised hand" status, if the current state allows
* toggling.
*/
maybeToggleRaisedHand() {
// If we are the dominant speaker, we don't enable "raise hand".
if (this.isHandRaised || !this.isDominantSpeaker) {
this.setRaisedHand(!this.isHandRaised);
}
},
/**
* Sets the local "raised hand" status to a particular value.
*/
setRaisedHand(raisedHand) {
if (raisedHand !== this.isHandRaised)
{
this.isHandRaised = raisedHand;
// Advertise the updated status
room.setLocalParticipantProperty("raisedHand", raisedHand);
// Update the view
APP.UI.setLocalRaisedHandStatus(raisedHand);
}
}
};

View File

@ -310,7 +310,7 @@
z-index: 3;
}
.videocontainer>span.dominantspeakerindicator {
.videocontainer>span.indicator {
bottom: 0px;
left: 0px;
width: 25px;
@ -327,7 +327,7 @@
border: 0px;
}
#speakerindicatoricon {
#indicatoricon {
padding-top: 5px;
}

2
debian/control vendored
View File

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

View File

@ -20,13 +20,13 @@ The next step for embedding Jitsi Meet is to create the Jitsi Meet API object
var height = 700;
var api = new JitsiMeetExternalAPI(domain, room, width, height);
</script>
```
```
You can paste that lines in your html code where you want to be placed the Jitsi Meet conference
or you can specify the parent HTML element for the Jitsi Meet conference in the JitsiMeetExternalAPI
constructor.
```javascript
var api = new JitsiMeetExternalAPI(domain, room, width, height, htmlElement);
```
```
If you don't specify room the user will enter in new conference with random room name.
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,
var configOverwrite = {enableSimulcast: false};
var interfaceConfigOverwrite = {filmStripOnly: true};
var api = new JitsiMeetExternalAPI(domain, room, width, height, htmlElement, true, configOverwrite, interfaceConfigOverwrite);
```
```
Controlling embedded Jitsi Meet Conference
=========
You can control the embedded Jitsi Meet conference using the JitsiMeetExternalAPI object.
You can send command to Jitsi Meet conference using ```executeCommand```.
You can send command to Jitsi Meet conference using ```executeCommand```.
```
api.executeCommand(command, arguments)
```
The ```command``` parameter is String object with the name of the command.
The ```arguments``` parameter is array with the arguments required by the command.
The ```arguments``` parameter is array with the arguments required by the command.
If no arguments are required by the command this parameter can be omitted or you can pass empty array.
Currently we support the following commands:
* **displayName** - sets the display name of the local participant. This command requires one argument -
* **displayName** - sets the display name of the local participant. This command requires one argument -
the new display name to be set
```
api.executeCommand('displayName', ['New Nickname']);
@ -77,7 +77,12 @@ api.executeCommand('toggleChat', [])
api.executeCommand('toggleContactList', [])
```
You can also execute multiple commands using the method ```executeCommands```.
* **toggleShareScreen** - starts / stops the screen sharing. No arguments are required.
```
api.executeCommand('toggleShareScreen', [])
```
You can also execute multiple commands using the method ```executeCommands```.
```
api.executeCommands(commands)
```
@ -136,9 +141,23 @@ The listener will receive object with the following structure:
jid: jid //the jid of the participant
}
```
* **video-conference-joined** - event notifications fired when the local user has joined the video conference.
The listener will receive object with the following structure:
```
{
roomName: room //the room name of the conference
}
```
* **video-conference-left** - event notifications fired when the local user has left the video conference.
The listener will receive object with the following structure:
```
{
roomName: room //the room name of the conference
}
```
You can also add multiple event listeners by using ```addEventListeners```.
This method requires one argument of type Object. The object argument must
This method requires one argument of type Object. The object argument must
have keys with the names of the events and values the listeners of the events.
```
@ -173,4 +192,4 @@ You can remove the embedded Jitsi Meet Conference with the following code:
api.dispose()
```
It is a good practice to remove the conference before the page is unloaded.
It is a good practice to remove the conference before the page is unloaded.

View File

@ -33,6 +33,11 @@ server {
ssi on;
}
# Backward compatibility
location ~ /external_api.* {
root /usr/share/jitsi-meet/libs;
}
# BOSH
location /http-bind {
proxy_pass http://localhost:5280/http-bind;

View File

@ -1,378 +0,0 @@
/**
* Implements API class that embeds Jitsi Meet in external applications.
*/
var JitsiMeetExternalAPI = (function()
{
/**
* The minimum width for the Jitsi Meet frame
* @type {number}
*/
var MIN_WIDTH = 790;
/**
* The minimum height for the Jitsi Meet frame
* @type {number}
*/
var MIN_HEIGHT = 300;
/**
* Constructs new API instance. Creates iframe element that loads
* Jitsi Meet.
* @param domain the domain name of the server that hosts the conference
* @param room_name the name of the room to join
* @param width width of the iframe
* @param height height of the iframe
* @param parent_node the node that will contain the iframe
* @param filmStripOnly if the value is true only the small videos will be
* visible.
* @param noSsl if the value is true https won't be used
* @constructor
*/
function JitsiMeetExternalAPI(domain, room_name, width, height, parentNode,
configOverwrite, interfaceConfigOverwrite, noSsl) {
if (!width || width < MIN_WIDTH)
width = MIN_WIDTH;
if (!height || height < MIN_HEIGHT)
height = MIN_HEIGHT;
this.parentNode = null;
if (parentNode) {
this.parentNode = parentNode;
} else {
var scriptTag = document.scripts[document.scripts.length - 1];
this.parentNode = scriptTag.parentNode;
}
this.iframeHolder =
this.parentNode.appendChild(document.createElement("div"));
this.iframeHolder.id = "jitsiConference" + JitsiMeetExternalAPI.id;
if(width)
this.iframeHolder.style.width = width + "px";
if(height)
this.iframeHolder.style.height = height + "px";
this.frameName = "jitsiConferenceFrame" + JitsiMeetExternalAPI.id;
this.url = (noSsl) ? "http" : "https" +"://" + domain + "/";
if(room_name)
this.url += room_name;
this.url += "#external=true";
var key;
if (configOverwrite) {
for (key in configOverwrite) {
if (!configOverwrite.hasOwnProperty(key) ||
typeof key !== 'string')
continue;
this.url += "&config." + key + "=" + configOverwrite[key];
}
}
if (interfaceConfigOverwrite) {
for (key in interfaceConfigOverwrite) {
if (!interfaceConfigOverwrite.hasOwnProperty(key) ||
typeof key !== 'string')
continue;
this.url += "&interfaceConfig." + key + "=" +
interfaceConfigOverwrite[key];
}
}
JitsiMeetExternalAPI.id++;
this.frame = document.createElement("iframe");
this.frame.src = this.url;
this.frame.name = this.frameName;
this.frame.id = this.frameName;
this.frame.width = "100%";
this.frame.height = "100%";
this.frame.setAttribute("allowFullScreen","true");
this.frame = this.iframeHolder.appendChild(this.frame);
this.frameLoaded = false;
this.initialCommands = [];
this.eventHandlers = {};
this.initListeners();
}
/**
* Last id of api object
* @type {number}
*/
JitsiMeetExternalAPI.id = 0;
/**
* Sends the passed object to Jitsi Meet
* @param object the object to be sent
*/
JitsiMeetExternalAPI.prototype.sendMessage = function(object) {
if (this.frameLoaded) {
this.frame.contentWindow.postMessage(
JSON.stringify(object), this.frame.src);
}
else {
this.initialCommands.push(object);
}
};
/**
* Executes command. The available commands are:
* displayName - sets the display name of the local participant to the value
* passed in the arguments array.
* toggleAudio - mutes / unmutes audio with no arguments
* toggleVideo - mutes / unmutes video with no arguments
* filmStrip - hides / shows the film strip with no arguments
* If the command doesn't require any arguments the parameter should be set
* to empty array or it may be omitted.
* @param name the name of the command
* @param arguments array of arguments
*/
JitsiMeetExternalAPI.prototype.executeCommand = function(name,
argumentsList) {
var argumentsArray = argumentsList;
if (!argumentsArray)
argumentsArray = [];
var object = {type: "command", action: "execute"};
object[name] = argumentsArray;
this.sendMessage(object);
};
/**
* Executes commands. The available commands are:
* displayName - sets the display name of the local participant to the value
* passed in the arguments array.
* toggleAudio - mutes / unmutes audio with no arguments
* toggleVideo - mutes / unmutes video with no arguments
* filmStrip - hides / shows the film strip with no arguments
* @param object the object with commands to be executed. The keys of the
* object are the commands that will be executed and the values are the
* arguments for the command.
*/
JitsiMeetExternalAPI.prototype.executeCommands = function (object) {
object.type = "command";
object.action = "execute";
this.sendMessage(object);
};
/**
* Adds event listeners to Meet Jitsi. The object key should be the name of
* the event and value - the listener.
* Currently we support the following
* events:
* incomingMessage - receives event notifications about incoming
* messages. The listener will receive object with the following structure:
* {{
* "from": from,//JID of the user that sent the message
* "nick": nick,//the nickname of the user that sent the message
* "message": txt//the text of the message
* }}
* outgoingMessage - receives event notifications about outgoing
* messages. The listener will receive object with the following structure:
* {{
* "message": txt//the text of the message
* }}
* displayNameChanged - receives event notifications about display name
* change. The listener will receive object with the following structure:
* {{
* jid: jid,//the JID of the participant that changed his display name
* displayname: displayName //the new display name
* }}
* participantJoined - receives event notifications about new participant.
* The listener will receive object with the following structure:
* {{
* jid: jid //the jid of the participant
* }}
* participantLeft - receives event notifications about the participant that
* left the room.
* The listener will receive object with the following structure:
* {{
* jid: jid //the jid of the participant
* }}
* @param object
*/
JitsiMeetExternalAPI.prototype.addEventListeners
= function (object) {
var message = {type: "event", action: "add", events: []};
for(var i in object)
{
message.events.push(i);
this.eventHandlers[i] = object[i];
}
this.sendMessage(message);
};
/**
* Adds event listeners to Meet Jitsi. Currently we support the following
* events:
* incomingMessage - receives event notifications about incoming
* messages. The listener will receive object with the following structure:
* {{
* "from": from,//JID of the user that sent the message
* "nick": nick,//the nickname of the user that sent the message
* "message": txt//the text of the message
* }}
* outgoingMessage - receives event notifications about outgoing
* messages. The listener will receive object with the following structure:
* {{
* "message": txt//the text of the message
* }}
* displayNameChanged - receives event notifications about display name
* change. The listener will receive object with the following structure:
* {{
* jid: jid,//the JID of the participant that changed his display name
* displayname: displayName //the new display name
* }}
* participantJoined - receives event notifications about new participant.
* The listener will receive object with the following structure:
* {{
* jid: jid //the jid of the participant
* }}
* participantLeft - receives event notifications about participant the that
* left the room.
* The listener will receive object with the following structure:
* {{
* jid: jid //the jid of the participant
* }}
* @param event the name of the event
* @param listener the listener
*/
JitsiMeetExternalAPI.prototype.addEventListener
= function (event, listener) {
var message = {type: "event", action: "add", events: [event]};
this.eventHandlers[event] = listener;
this.sendMessage(message);
};
/**
* Removes event listener.
* @param event the name of the event.
*/
JitsiMeetExternalAPI.prototype.removeEventListener
= function (event) {
if(!this.eventHandlers[event])
{
console.error("The event " + event + " is not registered.");
return;
}
var message = {type: "event", action: "remove", events: [event]};
delete this.eventHandlers[event];
this.sendMessage(message);
};
/**
* Removes event listeners.
* @param events array with the names of the events.
*/
JitsiMeetExternalAPI.prototype.removeEventListeners
= function (events) {
var eventsArray = [];
for(var i = 0; i < events.length; i++)
{
var event = events[i];
if(!this.eventHandlers[event])
{
console.error("The event " + event + " is not registered.");
continue;
}
delete this.eventHandlers[event];
eventsArray.push(event);
}
if(eventsArray.length > 0)
{
this.sendMessage(
{type: "event", action: "remove", events: eventsArray});
}
};
/**
* Processes message events sent from Jitsi Meet
* @param event the event
*/
JitsiMeetExternalAPI.prototype.processMessage = function(event) {
var message;
try {
message = JSON.parse(event.data);
} catch (e) {}
if(!message.type) {
console.error("Message without type is received.");
return;
}
switch (message.type) {
case "system":
if(message.loaded) {
this.onFrameLoaded();
}
break;
case "event":
if(message.action != "result" ||
!message.event || !this.eventHandlers[message.event]) {
console.warn("The received event cannot be parsed.");
return;
}
this.eventHandlers[message.event](message.result);
break;
default :
console.error("Unknown message type.");
return;
}
};
/**
* That method is called when the Jitsi Meet is loaded. Executes saved
* commands that are send before the frame was loaded.
*/
JitsiMeetExternalAPI.prototype.onFrameLoaded = function () {
this.frameLoaded = true;
for (var i = 0; i < this.initialCommands.length; i++) {
this.sendMessage(this.initialCommands[i]);
}
this.initialCommands = null;
};
/**
* Setups the listener for message events from Jitsi Meet.
*/
JitsiMeetExternalAPI.prototype.initListeners = function () {
var self = this;
this.eventListener = function (event) {
self.processMessage(event);
};
if (window.addEventListener) {
window.addEventListener('message',
this.eventListener, false);
}
else {
window.attachEvent('onmessage', this.eventListener);
}
};
/**
* Removes the listeners and removes the Jitsi Meet frame.
*/
JitsiMeetExternalAPI.prototype.dispose = function () {
if (window.removeEventListener) {
window.removeEventListener('message',
this.eventListener, false);
}
else {
window.detachEvent('onmessage',
this.eventListener);
}
var frame = document.getElementById(this.frameName);
if(frame)
frame.src = 'about:blank';
var self = this;
window.setTimeout(function () {
self.iframeHolder.removeChild(self.frame);
self.iframeHolder.parentNode.removeChild(self.iframeHolder);
}, 10);
};
return JitsiMeetExternalAPI;
})();

View File

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

View File

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

View File

@ -1,11 +1,11 @@
/* global APP */
/* global APP, getConfigParamsFromUrl */
/**
* Implements API class that communicates with external api class
* and provides interface to access Jitsi Meet features by external
* applications that embed Jitsi Meet
*/
import postis from 'postis';
import postisInit from 'postis';
/**
* List of the available commands.
@ -20,36 +20,41 @@
*/
let commands = {};
let hashParams = getConfigParamsFromUrl();
/**
* JitsiMeetExternalAPI id - unique for a webpage.
*/
let jitsi_meet_external_api_id = hashParams.jitsi_meet_external_api_id;
/**
* Object that will execute sendMessage
*/
let target = window.opener ? window.opener : window.parent;
/**
* Array of functions that are going to receive the objects passed to this
* window
* Postis instance. Used to communicate with the external application.
*/
let messageListeners = [];
let postis;
/**
* Current status (enabled/disabled) of Postis.
* Current status (enabled/disabled) of API.
*/
let enablePostis = false;
/**
* Current status (enabled/disabled) of Post Message API.
*/
let enablePostMessage = false;
let enabled = false;
function initCommands() {
commands = {
displayName: APP.UI.inputDisplayNameHandler,
toggleAudio: APP.conference.toggleAudioMuted,
toggleVideo: APP.conference.toggleVideoMuted,
toggleFilmStrip: APP.UI.toggleFilmStrip,
toggleChat: APP.UI.toggleChat,
toggleContactList: APP.UI.toggleContactList
"display-name": APP.UI.inputDisplayNameHandler,
"toggle-audio": APP.conference.toggleAudioMuted,
"toggle-video": APP.conference.toggleVideoMuted,
"toggle-film-strip": APP.UI.toggleFilmStrip,
"toggle-chat": APP.UI.toggleChat,
"toggle-contact-list": APP.UI.toggleContactList,
"toggle-share-screen": APP.conference.toggleScreenSharing
};
Object.keys(commands).forEach(function (key) {
postis.listen(key, commands[key]);
});
}
@ -57,95 +62,34 @@ function initCommands() {
* Maps the supported events and their status
* (true it the event is enabled and false if it is disabled)
* @type {{
* incomingMessage: boolean,
* outgoingMessage: boolean,
* displayNameChange: boolean,
* participantJoined: boolean,
* participantLeft: boolean
* incoming-message: boolean,
* outgoing-message: boolean,
* display-name-change: boolean,
* participant-left: boolean,
* participant-joined: boolean,
* video-conference-left: boolean,
* video-conference-joined: boolean
* }}
*/
const events = {
incomingMessage: false,
outgoingMessage:false,
displayNameChange: false,
participantJoined: false,
participantLeft: false
"incoming-message": false,
"outgoing-message":false,
"display-name-change": false,
"participant-joined": false,
"participant-left": false,
"video-conference-joined": false,
"video-conference-left": false
};
/**
* Processes commands from external application.
* @param message the object with the command
*/
function processCommand(message) {
if (message.action != "execute") {
console.error("Unknown action of the message");
return;
}
for (var key in message) {
if(commands[key])
commands[key].apply(null, message[key]);
}
}
/**
* Processes events objects from external applications
* @param event the event
*/
function processEvent(event) {
if (!event.action) {
console.error("Event with no action is received.");
return;
}
var i = 0;
switch(event.action) {
case "add":
for (; i < event.events.length; i++) {
events[event.events[i]] = true;
}
break;
case "remove":
for (; i < event.events.length; i++) {
events[event.events[i]] = false;
}
break;
default:
console.error("Unknown action for event.");
}
}
/**
* Processes a message event from the external application
* @param event the message event
*/
function processMessage(event) {
var message;
try {
message = JSON.parse(event.data);
} catch (e) {
console.error("Cannot parse data", event.data);
return;
}
switch (message.type) {
case "command":
processCommand(message);
break;
case "event":
processEvent(message);
break;
default:
console.warn("Unknown message type");
}
}
/**
* Sends message to the external application.
* @param object {object} the object that will be sent as JSON string
* @param message {object}
* @param method {string}
* @param params {object} the object that will be sent as JSON string
*/
function sendMessage(object) {
if(enablePostMessage)
target.postMessage(JSON.stringify(object), "*");
function sendMessage(message) {
if(enabled)
postis.send(message);
}
/**
@ -153,8 +97,7 @@ function sendMessage(object) {
* @returns {boolean}
*/
function isEnabled () {
let hash = location.hash;
return !!(hash && hash.indexOf("external=true") > -1 && window.postMessage);
return (typeof jitsi_meet_external_api_id === "number");
}
/**
@ -173,13 +116,25 @@ function isEventEnabled (name) {
* @param object data associated with the event
*/
function triggerEvent (name, object) {
if (isEventEnabled(name) && enablePostMessage) {
sendMessage({
type: "event",
action: "result",
event: name,
result: object
});
if(isEventEnabled(name))
sendMessage({method: name, params: object});
}
/**
* Handles system messages. (for example: enable/disable events)
* @param message {object} the message
*/
function onSystemMessage(message) {
switch (message.type) {
case "eventStatus":
if(!message.name || !message.value) {
console.warn("Unknown system message format", message);
break;
}
events[message.name] = message.value;
break;
default:
console.warn("Unknown system message type", message);
}
}
@ -190,33 +145,27 @@ export default {
* It also sends a message to the external application that APIConnector
* is initialized.
* @param options {object}
* @param enablePostis {boolean} if true the postis npm
* package for comminication with the parent window will be enabled.
* @param enablePostMessage {boolean} if true the postMessageAPI for
* comminication with the parent window will be enabled.
* @param forceEnable {boolean} if true the module will be enabled.
* @param enabledEvents {array} array of events that should be enabled.
*/
init: function (options = {}) {
options.enablePostMessage = options.enablePostMessage || isEnabled();
if (!options.enablePostis &&
!options.enablePostMessage) {
init (options = {}) {
if(!isEnabled() && !options.forceEnable)
return;
}
enablePostis = options.enablePostis;
enablePostMessage = options.enablePostMessage;
if(enablePostMessage) {
initCommands();
if (window.addEventListener) {
window.addEventListener('message', processMessage, false);
} else {
window.attachEvent('onmessage', processMessage);
}
sendMessage({type: "system", loaded: true});
}
if(enablePostis) {
this.postis = postis({window: target});
}
enabled = true;
if(options.enabledEvents)
options.enabledEvents.forEach(function (eventName) {
events[eventName] = true;
});
let postisOptions = {
window: target
};
if(typeof jitsi_meet_external_api_id === "number")
postisOptions.scope
= "jitsi_meet_external_api_" + jitsi_meet_external_api_id;
postis = postisInit(postisOptions);
postis.listen("jitsiSystemMessage", onSystemMessage);
initCommands();
},
/**
@ -224,28 +173,7 @@ export default {
* @param {string} body message body
*/
notifySendingChatMessage (body) {
triggerEvent("outgoingMessage", {"message": body});
},
/**
* Sends message to the external application.
* @param options {object}
* @param method {string}
* @param params {object} the object that will be sent as JSON string
*/
sendPostisMessage(options) {
if(enablePostis)
this.postis.send(options);
},
/**
* Adds listener for Postis messages.
* @param method {string} postis mehtod
* @param listener {function}
*/
addPostisMessageListener (method, listener) {
if(enablePostis)
this.postis.listen(method, listener);
triggerEvent("outgoing-message", {"message": body});
},
/**
@ -262,7 +190,7 @@ export default {
}
triggerEvent(
"incomingMessage",
"incoming-message",
{"from": id, "nick": nick, "message": body, "stamp": ts}
);
},
@ -273,7 +201,7 @@ export default {
* @param {string} id user id
*/
notifyUserJoined (id) {
triggerEvent("participantJoined", {id});
triggerEvent("participant-joined", {id});
},
/**
@ -282,7 +210,7 @@ export default {
* @param {string} id user id
*/
notifyUserLeft (id) {
triggerEvent("participantLeft", {id});
triggerEvent("participant-left", {id});
},
/**
@ -292,21 +220,34 @@ export default {
* @param {string} displayName user nickname
*/
notifyDisplayNameChanged (id, displayName) {
triggerEvent("displayNameChange", {id, displayname: displayName});
triggerEvent("display-name-change", {id, displayname: displayName});
},
/**
* Notify external application (if API is enabled) that
* user changed their nickname.
* @param {string} id user id
* @param {string} displayName user nickname
*/
notifyConferenceJoined (room) {
triggerEvent("video-conference-joined", {roomName: room});
},
/**
* Notify external application (if API is enabled) that
* user changed their nickname.
* @param {string} id user id
* @param {string} displayName user nickname
*/
notifyConferenceLeft (room) {
triggerEvent("video-conference-left", {roomName: room});
},
/**
* Removes the listeners.
*/
dispose: function () {
if (enablePostMessage) {
if (window.removeEventListener) {
window.removeEventListener("message", processMessage, false);
} else {
window.detachEvent('onmessage', processMessage);
}
}
if(enablePostis)
this.postis.destroy();
if(enabled)
postis.destroy();
}
};

359
modules/API/external/external_api.js vendored Normal file
View File

@ -0,0 +1,359 @@
/**
* Implements API class that embeds Jitsi Meet in external applications.
*/
var postisInit = require("postis");
/**
* The minimum width for the Jitsi Meet frame
* @type {number}
*/
var MIN_WIDTH = 790;
/**
* The minimum height for the Jitsi Meet frame
* @type {number}
*/
var MIN_HEIGHT = 300;
/**
* Last id of api object
* @type {number}
*/
var id = 0;
/**
* Maps the names of the commands expected by the API with the name of the
* commands expected by jitsi-meet
*/
var commands = {
"displayName": "display-name",
"toggleAudio": "toggle-audio",
"toggleVideo": "toggle-video",
"toggleFilmStrip": "toggle-film-strip",
"toggleChat": "toggle-chat",
"toggleContactList": "toggle-contact-list",
"toggleShareScreen": "toggle-share-screen"
};
/**
* Maps the names of the events expected by the API with the name of the
* events expected by jitsi-meet
*/
var events = {
"incomingMessage": "incoming-message",
"outgoingMessage": "outgoing-message",
"displayNameChange": "display-name-change",
"participantJoined": "participant-joined",
"participantLeft": "participant-left",
"videoConferenceJoined": "video-conference-joined",
"videoConferenceLeft": "video-conference-left"
};
/**
* Sends the passed object to Jitsi Meet
* @param postis {Postis object} the postis instance that is going to be used
* to send the message
* @param object the object to be sent
* - method {sting}
* - params {object}
*/
function sendMessage(postis, object) {
postis.send(object);
}
/**
* Sends message for event enable/disable status change.
* @param postis {Postis object} the postis instance that is going to be used.
* @param event {string} the name of the event
* @param status {boolean} true - enabled; false - disabled;
*/
function changeEventStatus(postis, event, status) {
if(!(event in events)) {
console.error("Not supported event name.");
return;
}
sendMessage(postis, {
method: "jitsiSystemMessage",
params: {type: "eventStatus", name: events[event], value: status}
});
}
/**
* Constructs new API instance. Creates iframe element that loads
* Jitsi Meet.
* @param domain the domain name of the server that hosts the conference
* @param room_name the name of the room to join
* @param width width of the iframe
* @param height height of the iframe
* @param parent_node the node that will contain the iframe
* @param filmStripOnly if the value is true only the small videos will be
* visible.
* @param noSsl if the value is true https won't be used
* @constructor
*/
function JitsiMeetExternalAPI(domain, room_name, width, height, parentNode,
configOverwrite, interfaceConfigOverwrite, noSsl) {
if (!width || width < MIN_WIDTH)
width = MIN_WIDTH;
if (!height || height < MIN_HEIGHT)
height = MIN_HEIGHT;
this.parentNode = null;
if (parentNode) {
this.parentNode = parentNode;
} else {
var scriptTag = document.scripts[document.scripts.length - 1];
this.parentNode = scriptTag.parentNode;
}
this.iframeHolder =
this.parentNode.appendChild(document.createElement("div"));
this.iframeHolder.id = "jitsiConference" + id;
if(width)
this.iframeHolder.style.width = width + "px";
if(height)
this.iframeHolder.style.height = height + "px";
this.frameName = "jitsiConferenceFrame" + id;
this.url = (noSsl) ? "http" : "https" +"://" + domain + "/";
if(room_name)
this.url += room_name;
this.url += "#jitsi_meet_external_api_id=" + id;
var key;
if (configOverwrite) {
for (key in configOverwrite) {
if (!configOverwrite.hasOwnProperty(key) ||
typeof key !== 'string')
continue;
this.url += "&config." + key + "=" + configOverwrite[key];
}
}
if (interfaceConfigOverwrite) {
for (key in interfaceConfigOverwrite) {
if (!interfaceConfigOverwrite.hasOwnProperty(key) ||
typeof key !== 'string')
continue;
this.url += "&interfaceConfig." + key + "=" +
interfaceConfigOverwrite[key];
}
}
this.frame = document.createElement("iframe");
this.frame.src = this.url;
this.frame.name = this.frameName;
this.frame.id = this.frameName;
this.frame.width = "100%";
this.frame.height = "100%";
this.frame.setAttribute("allowFullScreen","true");
this.frame = this.iframeHolder.appendChild(this.frame);
this.postis = postisInit({
window: this.frame.contentWindow,
scope: "jitsi_meet_external_api_" + id
});
this.eventHandlers = {};
id++;
}
/**
* Executes command. The available commands are:
* displayName - sets the display name of the local participant to the value
* passed in the arguments array.
* toggleAudio - mutes / unmutes audio with no arguments
* toggleVideo - mutes / unmutes video with no arguments
* filmStrip - hides / shows the film strip with no arguments
* If the command doesn't require any arguments the parameter should be set
* to empty array or it may be omitted.
* @param name the name of the command
* @param arguments array of arguments
*/
JitsiMeetExternalAPI.prototype.executeCommand = function(name, argumentsList) {
if(!(name in commands)) {
console.error("Not supported command name.");
return;
}
var argumentsArray = argumentsList;
if (!argumentsArray)
argumentsArray = [];
sendMessage(this.postis, {method: commands[name], params: argumentsArray});
};
/**
* Executes commands. The available commands are:
* displayName - sets the display name of the local participant to the value
* passed in the arguments array.
* toggleAudio - mutes / unmutes audio. no arguments
* toggleVideo - mutes / unmutes video. no arguments
* filmStrip - hides / shows the film strip. no arguments
* toggleChat - hides / shows chat. no arguments.
* toggleContactList - hides / shows contact list. no arguments.
* toggleShareScreen - starts / stops screen sharing. no arguments.
* @param object the object with commands to be executed. The keys of the
* object are the commands that will be executed and the values are the
* arguments for the command.
*/
JitsiMeetExternalAPI.prototype.executeCommands = function(object) {
for(var key in object)
this.executeCommand(key, object[key]);
};
/**
* Adds event listeners to Meet Jitsi. The object key should be the name of
* the event and value - the listener.
* Currently we support the following
* events:
* incomingMessage - receives event notifications about incoming
* messages. The listener will receive object with the following structure:
* {{
* "from": from,//JID of the user that sent the message
* "nick": nick,//the nickname of the user that sent the message
* "message": txt//the text of the message
* }}
* outgoingMessage - receives event notifications about outgoing
* messages. The listener will receive object with the following structure:
* {{
* "message": txt//the text of the message
* }}
* displayNameChanged - receives event notifications about display name
* change. The listener will receive object with the following structure:
* {{
* jid: jid,//the JID of the participant that changed his display name
* displayname: displayName //the new display name
* }}
* participantJoined - receives event notifications about new participant.
* The listener will receive object with the following structure:
* {{
* jid: jid //the jid of the participant
* }}
* participantLeft - receives event notifications about the participant that
* left the room.
* The listener will receive object with the following structure:
* {{
* jid: jid //the jid of the participant
* }}
* video-conference-joined - receives event notifications about the local user
* has successfully joined the video conference.
* The listener will receive object with the following structure:
* {{
* roomName: room //the room name of the conference
* }}
* video-conference-left - receives event notifications about the local user
* has left the video conference.
* The listener will receive object with the following structure:
* {{
* roomName: room //the room name of the conference
* }}
* @param object
*/
JitsiMeetExternalAPI.prototype.addEventListeners = function(object) {
for(var i in object)
this.addEventListener(i, object[i]);
};
/**
* Adds event listeners to Meet Jitsi. Currently we support the following
* events:
* incomingMessage - receives event notifications about incoming
* messages. The listener will receive object with the following structure:
* {{
* "from": from,//JID of the user that sent the message
* "nick": nick,//the nickname of the user that sent the message
* "message": txt//the text of the message
* }}
* outgoingMessage - receives event notifications about outgoing
* messages. The listener will receive object with the following structure:
* {{
* "message": txt//the text of the message
* }}
* displayNameChanged - receives event notifications about display name
* change. The listener will receive object with the following structure:
* {{
* jid: jid,//the JID of the participant that changed his display name
* displayname: displayName //the new display name
* }}
* participantJoined - receives event notifications about new participant.
* The listener will receive object with the following structure:
* {{
* jid: jid //the jid of the participant
* }}
* participantLeft - receives event notifications about participant the that
* left the room.
* The listener will receive object with the following structure:
* {{
* jid: jid //the jid of the participant
* }}
* video-conference-joined - receives event notifications fired when the local
* user has joined the video conference.
* The listener will receive object with the following structure:
* {{
* roomName: room //the room name of the conference
* }}
* video-conference-left - receives event notifications fired when the local
* user has joined the video conference.
* The listener will receive object with the following structure:
* {{
* roomName: room //the room name of the conference
* }}
* @param event the name of the event
* @param listener the listener
*/
JitsiMeetExternalAPI.prototype.addEventListener = function(event, listener) {
if(!(event in events)) {
console.error("Not supported event name.");
return;
}
// We cannot remove listeners from postis that's why we are handling the
// callback that way.
if(!(event in this.eventHandlers))
this.postis.listen(events[event], function(data) {
if((event in this.eventHandlers) &&
typeof this.eventHandlers[event] === "function")
this.eventHandlers[event].call(null, data);
}.bind(this));
this.eventHandlers[event] = listener;
changeEventStatus(this.postis, event, true);
};
/**
* Removes event listener.
* @param event the name of the event.
*/
JitsiMeetExternalAPI.prototype.removeEventListener = function(event) {
if(!(event in this.eventHandlers))
{
console.error("The event " + event + " is not registered.");
return;
}
delete this.eventHandlers[event];
changeEventStatus(this.postis, event, false);
};
/**
* Removes event listeners.
* @param events array with the names of the events.
*/
JitsiMeetExternalAPI.prototype.removeEventListeners = function(events) {
var eventsArray = [];
for(var i = 0; i < events.length; i++)
this.removeEventListener(events[i]);
};
/**
* Removes the listeners and removes the Jitsi Meet frame.
*/
JitsiMeetExternalAPI.prototype.dispose = function() {
this.postis.dispose();
var frame = document.getElementById(this.frameName);
if(frame)
frame.src = 'about:blank';
var self = this;
window.setTimeout(function () {
self.iframeHolder.removeChild(self.frame);
self.iframeHolder.parentNode.removeChild(self.iframeHolder);
}, 10);
};
module.exports = JitsiMeetExternalAPI;

View File

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

View File

@ -40,6 +40,8 @@ let sharedVideoManager;
let followMeHandler;
let deviceErrorDialog;
const TrackErrors = JitsiMeetJS.errors.track;
const JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP = {
@ -256,7 +258,25 @@ UI.changeDisplayName = function (id, displayName) {
};
/**
* Intitialize conference UI.
* Sets the "raised hand" status for a participant.
*/
UI.setRaisedHandStatus = (participant, raisedHandStatus) => {
VideoLayout.setRaisedHandStatus(participant.getId(), raisedHandStatus);
if (raisedHandStatus) {
messageHandler.notify(participant.getDisplayName(), 'notify.somebody',
'connected', 'notify.raisedHand');
}
};
/**
* Sets the local "raised hand" status.
*/
UI.setLocalRaisedHandStatus = (raisedHandStatus) => {
VideoLayout.setRaisedHandStatus(APP.conference.localId, raisedHandStatus);
};
/**
* Initialize conference UI.
*/
UI.initConference = function () {
let id = APP.conference.localId;
@ -1268,7 +1288,11 @@ UI.showDeviceErrorDialog = function (micError, cameraError) {
message = `${message}${doNotShowWarningAgainSection}`;
messageHandler.openDialog(
// To make sure we don't have multiple error dialogs open at the same time,
// we will just close the previous one if we are going to show a new one.
deviceErrorDialog && deviceErrorDialog.close();
deviceErrorDialog = messageHandler.openDialog(
titleMsg,
message,
false,
@ -1284,6 +1308,12 @@ UI.showDeviceErrorDialog = function (micError, cameraError) {
input.prop("checked");
}
}
},
null,
function () {
// Reset dialog reference to null to avoid memory leaks when
// user closed the dialog manually.
deviceErrorDialog = null;
}
);
@ -1403,10 +1433,21 @@ UI.hideUserMediaPermissionsGuidanceOverlay = function () {
};
/**
* Shows or hides the keyboard shortcuts panel.'
* Shows or hides the keyboard shortcuts panel, depending on the current state.'
*/
UI.toggleKeyboardShortcutsPanel = function() {
$('#keyboard-shortcuts').toggle();
};
/**
* Shows or hides the keyboard shortcuts panel.'
*/
UI.showKeyboardShortcutsPanel = function(show) {
if (show) {
$('#keyboard-shortcuts').show();
} else {
$('#keyboard-shortcuts').hide();
}
};
module.exports = UI;

View File

@ -113,9 +113,10 @@ var messageHandler = {
* @param submitFunction function to be called on submit
* @param loadedFunction function to be called after the prompt is fully
* loaded
* @param closeFunction function to be called on dialog close
*/
openDialog: function (titleString, msgString, persistent, buttons,
submitFunction, loadedFunction) {
submitFunction, loadedFunction, closeFunction) {
if (!popupEnabled)
return;
@ -125,11 +126,14 @@ var messageHandler = {
buttons: buttons,
defaultButton: 1,
loaded: loadedFunction,
submit: submitFunction
submit: submitFunction,
close: closeFunction
};
if (persistent) {
args.closeText = '';
}
return new Impromptu(msgString, args);
},
@ -215,16 +219,19 @@ var messageHandler = {
},
/**
* Displayes notification.
* @param displayName display name of the participant that is associated with the notification.
* @param displayNameKey the key from the language file for the display name.
* Displays a notification.
* @param displayName the display name of the participant that is
* associated with the notification.
* @param displayNameKey the key from the language file for the display
* name. Only used if displayName i not provided.
* @param cls css class for the notification
* @param messageKey the key from the language file for the text of the message.
* @param messageKey the key from the language file for the text of the
* message.
* @param messageArguments object with the arguments for the message.
* @param options object with language options.
*/
notify: function(displayName, displayNameKey,
cls, messageKey, messageArguments, options) {
notify: function(displayName, displayNameKey, cls, messageKey,
messageArguments, options) {
if(!notificationsEnabled)
return;

View File

@ -422,37 +422,69 @@ SmallVideo.prototype.avatarChanged = function (avatarUrl) {
};
/**
* Updates the Indicator for dominant speaker.
*
* @param isSpeaker indicates the current indicator state
* Shows or hides the dominant speaker indicator.
* @param show whether to show or hide.
*/
SmallVideo.prototype.updateDominantSpeakerIndicator = function (isSpeaker) {
SmallVideo.prototype.showDominantSpeakerIndicator = function (show) {
if (!this.container) {
console.warn( "Unable to set dominant speaker indicator - "
+ this.videoSpanId + " does not exist");
return;
}
var indicatorSpan
= $('#' + this.videoSpanId + '>span.dominantspeakerindicator');
var indicatorSpanId = "dominantspeakerindicator";
var indicatorSpan = this.getIndicatorSpan(indicatorSpanId);
// If we do not have an indicator for this video.
if (indicatorSpan.length <= 0) {
indicatorSpan = document.createElement('span');
indicatorSpan.innerHTML
= "<i id='indicatoricon' class='fa fa-bullhorn'></i>";
// adds a tooltip
UIUtil.setTooltip(indicatorSpan, "speaker", "left");
APP.translation.translateElement($(indicatorSpan));
indicatorSpan.innerHTML
= "<i id='speakerindicatoricon' class='fa fa-bullhorn'></i>";
indicatorSpan.className = 'dominantspeakerindicator';
$(indicatorSpan).css("visibility", show ? "visible" : "hidden");
};
$('#' + this.videoSpanId)[0].appendChild(indicatorSpan);
// adds a tooltip
UIUtil.setTooltip(indicatorSpan, "speaker", "left");
APP.translation.translateElement($(indicatorSpan));
/**
* Shows or hides the raised hand indicator.
* @param show whether to show or hide.
*/
SmallVideo.prototype.showRaisedHandIndicator = function (show) {
if (!this.container) {
console.warn( "Unable to raised hand indication - "
+ this.videoSpanId + " does not exist");
return;
}
$(indicatorSpan).css("visibility", isSpeaker ? "visible" : "hidden");
var indicatorSpanId = "raisehandindicator";
var indicatorSpan = this.getIndicatorSpan(indicatorSpanId);
indicatorSpan.style.background = "#D6D61E";
indicatorSpan.innerHTML
= "<i id='indicatoricon' class='fa fa-hand-paper-o'></i>";
// adds a tooltip
UIUtil.setTooltip(indicatorSpan, "raisedHand", "left");
APP.translation.translateElement($(indicatorSpan));
$(indicatorSpan).css("visibility", show ? "visible" : "hidden");
};
/**
* Gets (creating if necessary) the "indicator" span for this SmallVideo
identified by an ID.
*/
SmallVideo.prototype.getIndicatorSpan = function(id) {
var indicatorSpan;
var spans = $(`#${this.videoSpanId}>[id=${id}`);
if (spans.length <= 0) {
indicatorSpan = document.createElement('span');
indicatorSpan.id = id;
indicatorSpan.className = "indicator";
$('#' + this.videoSpanId)[0].appendChild(indicatorSpan);
} else {
indicatorSpan = spans[0];
}
return indicatorSpan;
};
export default SmallVideo;

View File

@ -562,6 +562,18 @@ var VideoLayout = {
}
},
/**
* Sets the "raised hand" status for a participant identified by 'id'.
*/
setRaisedHandStatus(id, raisedHandStatus) {
var video
= APP.conference.isLocalId(id)
? localVideoThumbnail : remoteVideos[id];
if (video) {
video.showRaisedHandIndicator(raisedHandStatus);
}
},
/**
* On dominant speaker changed event.
*/
@ -576,10 +588,10 @@ var VideoLayout = {
if (APP.conference.isLocalId(id)) {
if(oldSpeakerRemoteVideo)
{
oldSpeakerRemoteVideo.updateDominantSpeakerIndicator(false);
localVideoThumbnail.updateDominantSpeakerIndicator(true);
oldSpeakerRemoteVideo.showDominantSpeakerIndicator(false);
currentDominantSpeaker = null;
}
localVideoThumbnail.showDominantSpeakerIndicator(true);
return;
}
@ -589,12 +601,12 @@ var VideoLayout = {
}
// Update the current dominant speaker.
remoteVideo.updateDominantSpeakerIndicator(true);
localVideoThumbnail.updateDominantSpeakerIndicator(false);
remoteVideo.showDominantSpeakerIndicator(true);
localVideoThumbnail.showDominantSpeakerIndicator(false);
// let's remove the indications from the remote video if any
if (oldSpeakerRemoteVideo) {
oldSpeakerRemoteVideo.updateDominantSpeakerIndicator(false);
oldSpeakerRemoteVideo.showDominantSpeakerIndicator(false);
}
currentDominantSpeaker = id;

View File

@ -197,15 +197,17 @@ export default {
micDeviceId: micDeviceId
})
// If we fail to do this, try to create them separately.
.catch(() => Promise.all(
[createAudioTrack(false), createVideoTrack(false)]))
.then((audioTracks, videoTracks) => {
.catch(() => Promise.all([
createAudioTrack(false).then(([stream]) => stream),
createVideoTrack(false).then(([stream]) => stream)
]))
.then(tracks => {
if (audioTrackError || videoTrackError) {
APP.UI.showDeviceErrorDialog(
audioTrackError, videoTrackError);
}
return (audioTracks || []).concat(videoTracks || []);
return tracks.filter(t => typeof t !== 'undefined');
});
} else if (videoRequested && !audioRequested) {
return createVideoTrack();

View File

@ -3,13 +3,10 @@
var shortcuts = {};
function initShortcutHandlers() {
shortcuts = {
191: {
character: "/",
function: function(e) {
// Only trigger on "?", not on "/".
if (e.shiftKey) {
APP.UI.toggleKeyboardShortcutsPanel();
}
27: {
character: "Esc",
function: function() {
APP.UI.showKeyboardShortcutsPanel(false);
}
},
67: {
@ -40,6 +37,13 @@ function initShortcutHandlers() {
APP.conference.toggleAudioMuted();
}
},
82: {
character: "R",
function: function() {
APP.conference.maybeToggleRaisedHand();
}
},
84: {
character: "T",
function: function() {
@ -52,6 +56,15 @@ function initShortcutHandlers() {
function: function() {
APP.conference.toggleVideoMuted();
}
},
191: {
character: "/",
function: function(e) {
// Only trigger on "?", not on "/".
if (e.shiftKey) {
APP.UI.toggleKeyboardShortcutsPanel();
}
}
}
};
}