Compare commits

...

72 Commits
1024 ... 1069

Author SHA1 Message Date
Дамян Минков
f39f8d14fd Merge pull request #706 from jitsi/ui-improvements-and-styles
UI improvements and styles
2016-06-24 14:43:51 -05:00
yanas
770b003163 Change global font, unify fonts, colors and more 2016-06-24 14:09:34 -05:00
Дамян Минков
702b177e06 Merge pull request #708 from jitsi/gsm_bars_fix
Fixes issue with gsm bars for remote participants are always full
2016-06-24 12:26:48 -05:00
hristoterezov
3c0295e294 Removes console.debug 2016-06-24 12:25:21 -05:00
hristoterezov
970e8c764c Fixes issue with gsm bars for remote participants are always full 2016-06-24 12:22:51 -05:00
yanas
1d393f5786 Merge pull request #701 from tsareg/gum_permission_dialog_guidance
Show overlay with guidance for gUM permission prompts
2016-06-24 11:17:38 -05:00
tsareg
f03b228eea Merge remote-tracking branch 'remotes/upstream/master' into gum_permission_dialog_guidance 2016-06-24 13:02:58 +03:00
tsareg
d149ba6fc5 Fire an optional JitsiMediaDevices.PERMISSION_PROMPT_IS_SHOWN event when browser shows user media permission prompt when calling createLocalTracks 2016-06-24 12:47:13 +03:00
Дамян Минков
f3dc6f15e4 Merge pull request #705 from bgrozev/esc-shortcut
Hide the shortcuts panel when the Escape key is pressed.
2016-06-23 16:24:10 -05:00
tsareg
8ca282079a Changes after code review 2016-06-23 11:03:26 +03:00
yanas
769644a63f Merge pull request #700 from bgrozev/raise-hand2
Raise hand2
2016-06-22 21:59:22 -05:00
Boris Grozev
2cefea3677 Hide the shortcuts panel when the Escape key is pressed. 2016-06-22 13:30:00 -05:00
bgrozev
2e802c0f6d Merge pull request #698 from jitsi/external_api
Changes the implementation of the iframe API to use postis
2016-06-22 13:12:49 -05:00
hristoterezov
d29e39c1d2 Adds libs directory to .gitignore 2016-06-22 13:11:48 -05:00
hristoterezov
09fb5e5667 Merge pull request #703 from jitsi/fix-avatar-url-string
Fixes avatar URL setting string
2016-06-21 15:03:02 -05:00
yanas
70e5ce7aec Fixes avatar URL setting string 2016-06-21 15:01:43 -05:00
Любомир Маринов
1f942aa13d Merge pull request #697 from tsareg/fix_two_gum_error_dialogs
Fixing various edge-cases when two gUM error dialogs might be shown and other possible bugs
2016-06-21 12:49:10 -05:00
hristoterezov
b60095df28 Merge pull request #702 from jitsi/deb-update-deps
Removes unused dependency.
2016-06-21 10:52:20 -05:00
damencho
6715d41f92 Removes unused dependency.
Used to minimize strophe-plugins which were inside the source tree and now npm handles them.
2016-06-21 10:38:31 -05:00
tsareg
375b145030 Prevent possible memory leak 2016-06-21 17:39:00 +03:00
tsareg
9d3b2aee02 Show overlay with guidance for gUM permission prompts 2016-06-21 12:08:32 +03:00
Boris Grozev
2d2e27b8d0 Implements "raised hand". 2016-06-20 16:58:54 -05:00
Boris Grozev
4b6ac38058 Fixes a failure to show the dominant speaker indicator for the local participant. 2016-06-20 15:58:08 -05:00
hristoterezov
21c2469dd6 Removes unnecessary whitespaces from Makefile 2016-06-20 13:23:00 -05:00
hristoterezov
02f176c75a Changes the implementation of the iframe API to use postis 2016-06-17 15:35:40 -05:00
tsareg
8b528b582f Fixing various edge-cases when two gUM error dialogs might be shown and other possible bugs 2016-06-17 15:31:25 +03:00
Дамян Минков
72d38ad202 Merge pull request #696 from jitsi/add-pootle-langs
Add missing languages from Pootle
2016-06-15 12:53:59 -05:00
Ingo Bauersachs
7a5461e1cb Add missing languages from Pootle 2016-06-15 11:50:04 +02:00
Ingo Bauersachs
1714ede6d4 Rename Translation.md to readme.md 2016-06-15 11:45:03 +02:00
ibauersachs
f8ee97a71c Commit from translate.jitsi.org by user ibauersachs.: 240 of 240 strings translated (0 fuzzy). 2016-06-15 09:47:54 +00:00
tsareg
897a6bfbe6 Refactored conference.js code. Moved almost all code that relates to handling change of media devices to separate module. Fixed couple of bugs. 2016-06-14 20:40:15 -05:00
yanas
97237470af Visual improvements of keyboard shortcut popup 2016-06-14 20:21:32 -05:00
Boris Grozev
d79971a737 An initial version of a "keyboard shortcuts" help panel. 2016-06-14 20:16:38 -05:00
Дамян Минков
334f7bf95a Merge pull request #693 from jitsi/jwt_support
JWT client support
2016-06-14 14:38:36 -05:00
hristoterezov
661795fd51 Fixes accidentally changed value of strophejs-plugins property in package.json 2016-06-14 13:36:46 -05:00
hristoterezov
47fe71c1f1 Fixes issue with ToolbarToggle.setAlwaysVisibleToolbar 2016-06-14 11:34:56 -05:00
hristoterezov
c5eebcda98 Adds exception for preventing to send the JWT token 2016-06-13 16:43:15 -05:00
hristoterezov
8deb003ef6 JWT client support 2016-06-13 16:11:44 -05:00
George Politis
10b2746a3e Merge pull request #689 from bgrozev/makefile-improvements
Makefile improvements
2016-06-10 15:22:18 -05:00
Boris Grozev
62fd07e98e Copy the map files for the non-minified version to the deploy dir. 2016-06-10 19:44:27 +00:00
Boris Grozev
ee8a270a36 Don't run npm update, because it sometimes causes the lib-jitsi-meet npm link to be removed. 2016-06-10 19:43:29 +00:00
hristoterezov
870a4e705b Merge pull request #683 from jitsi/fixes-ff-warnings-2
Defines^2 the document encoding.
2016-06-09 10:41:43 -05:00
Любомир Маринов
9dcb717a51 Merge pull request #685 from tsareg/fix_type_error_when_enabling_permissions_for_devices
Fix TypeError when one of audioTracks or videoTracks was undefined
2016-06-09 09:37:02 -05:00
tsareg
f72e3bf552 Fix TypeError when one of audioTracks or videoTracks was undefined 2016-06-09 12:38:30 +03:00
George Politis
ef70ff7da0 Defines^2 the document encoding. 2016-06-08 23:51:45 -05:00
hristoterezov
61fa2d8ed1 Merge pull request #682 from jitsi/fixes-ff-warnings
Defines the document encoding.
2016-06-08 23:49:02 -05:00
George Politis
1bda4ca61c Defines the document encoding.
Make FF stop complaining about the character encoding of the HTML document
not being declared.
2016-06-08 18:35:28 -05:00
hristoterezov
ba00080462 Merge pull request #681 from jitsi/add-enable-disable-popup
Unifrms messageHandler access and adds enable disable
2016-06-08 15:05:03 -05:00
yanas
57815cb2fe Unifrms messageHandler access and adds enable disable 2016-06-08 14:48:45 -05:00
yanas
346ff889ea Merge pull request #679 from damencho/follow-me
Fixes follow-me to work without etherpad
2016-06-07 17:01:45 -05:00
damencho
165507b83a Removes printing audio levels by default in debug mode and makes it optional. 2016-06-07 16:40:43 -05:00
damencho
955e01a750 Adds comments for processing nextOnStage. 2016-06-07 16:39:08 -05:00
damencho
ca62f9bec2 Adds a check to make follow me work without etherpad enabled. 2016-06-07 15:13:28 -05:00
Emil Ivov
c82bf2a19c Merge pull request #678 from geekgonecrazy/master
Switch to https on iframe creation
2016-06-07 14:50:21 -05:00
Aaron Ogle
98919e0996 Changed variable from ssl to noSsl
Defaults to SSL, only if the noSsl flag is true will it use http
2016-06-07 14:08:02 -05:00
Aaron Ogle
81437263b4 Allow ssl variable to force https:// on the iframe 2016-06-06 23:06:37 -05:00
Aaron Ogle
f883199f4f Switch to https on iframe creation 2016-06-06 21:56:07 -05:00
damencho
207e6e1b7d Merge pull request #666 from champtar/source-package
Fix missing base.html in source-package
2016-06-06 17:19:45 -05:00
lyubomir
06911c4c75 Merge pull request #676 from jitsi/tsareg-handle_create_local_tracks_errors_better
Tsareg handle create local tracks errors better
2016-06-03 16:17:34 -05:00
Lyubomir Marinov
fa1ea94c5c Merge branch 'handle_create_local_tracks_errors_better' of https://github.com/tsareg/jitsi-meet into tsareg-handle_create_local_tracks_errors_better 2016-06-03 14:28:09 -05:00
hristoterezov
6b704f184b Merge pull request #675 from aaronkvanmeerten/master
Only push to history with present page URL plus room name
2016-06-03 14:13:49 -05:00
Aaron van Meerten
c2eede2bb5 Only push to history with present page URL plus room name
Use location.href instead of location.pathname to make the URL absolute
2016-06-03 13:00:09 -05:00
lyubomir
0fec9565e5 Merge pull request #668 from jitsi/jetty-ssi-impl
Adds property for initial jetty ssi configuration.
2016-06-03 09:56:43 -05:00
Emil Ivov
8114152369 Merge pull request #673 from jitsi/revert-history-pushState
Revert "Removes unnecessary history.pushState if the welcome page is …
2016-06-03 07:47:53 -05:00
yanas
968521ef7c Revert "Removes unnecessary history.pushState if the welcome page is disabled and the user enter the base URL"
This reverts commit 3d5af92c7a.
2016-06-03 07:45:57 -05:00
damencho
ed9fd6c8fd Adds property for initial jetty ssi configuration. 2016-06-02 12:27:47 -05:00
Etienne CHAMPETIER
5d9d6b4642 Fix missing base.html in source-package
Signed-off-by: Etienne CHAMPETIER <champetier.etienne@gmail.com>
2016-06-01 15:58:55 +02:00
tsareg
f574dbe056 Changes after code review 2016-05-27 18:49:26 +03:00
tsareg
ccdba03888 Minor fixes for error dialogs 2016-05-27 14:01:43 +03:00
tsareg
e257a3dfc9 Merge branch 'master' into handle_create_local_tracks_errors_better
# Please enter a commit message to explain why this merge is necessary,
# especially if it merges an updated upstream into a topic branch.
#
# Lines starting with '#' will be ignored, and an empty message aborts
# the commit.
2016-05-27 11:29:23 +03:00
tsareg
448fcf36b6 Show dialog for GUM errors 2016-05-26 11:53:02 +03:00
tsareg
48b219111d Use special JitsiTrackError object instead just strings for various types of errors that may happen to JitsiTrack 2016-05-25 15:04:48 +03:00
65 changed files with 4334 additions and 1117 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

@@ -3,22 +3,26 @@ BROWSERIFY = ./node_modules/.bin/browserify
UGLIFYJS = ./node_modules/.bin/uglifyjs
EXORCIST = ./node_modules/.bin/exorcist
CLEANCSS = ./node_modules/.bin/cleancss
CSS_FILES = font.css toastr.css main.css videolayout_default.css font-awesome.css jquery-impromptu.css modaldialog.css notice.css popup_menu.css login_menu.css popover.css jitsi_popover.css contact_list.css chat.css welcome_page.css settingsmenu.css feedback.css jquery.contextMenu.css
CSS_FILES = font.css toastr.css main.css overlay.css videolayout_default.css font-awesome.css jquery-impromptu.css modaldialog.css notice.css popup_menu.css login_menu.css popover.css jitsi_popover.css contact_list.css chat.css welcome_page.css settingsmenu.css feedback.css jquery.contextMenu.css keyboard-shortcuts.css
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) update
$(NPM) install
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
@@ -27,13 +31,16 @@ 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 $(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:
cp $(LIBJITSIMEET_DIR)/lib-jitsi-meet.min.js \
$(LIBJITSIMEET_DIR)/lib-jitsi-meet.min.map \
$(LIBJITSIMEET_DIR)/lib-jitsi-meet.js \
$(LIBJITSIMEET_DIR)/lib-jitsi-meet.js.map \
$(LIBJITSIMEET_DIR)/connection_optimization/external_connect.js \
$(DEPLOY_DIR)
deploy-css:
@@ -45,10 +52,13 @@ 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 && \
cp -r *.js connection_optimization favicon.ico fonts images index.html libs plugin.*html sounds title.html unsupported_browser.html LICENSE lang source_package/jitsi-meet && \
cp -r *.js *.html connection_optimization favicon.ico fonts images libs sounds LICENSE lang source_package/jitsi-meet && \
cp css/all.css source_package/jitsi-meet/css && \
cp css/unsupported_browser.css source_package/jitsi-meet/css && \
(cd source_package ; tar cjf ../jitsi-meet.tar.bz2 jitsi-meet) && \

40
app.js
View File

@@ -23,6 +23,25 @@ import conference from './conference';
import API from './modules/API/API';
import UIEvents from './service/UI/UIEvents';
import getTokenData from "./modules/TokenData/TokenData";
/**
* Tries to push history state with the following parameters:
* 'VideoChat', `Room: ${roomName}`, URL. If fail, prints the error and returns
* it.
*/
function pushHistoryState(roomName, URL) {
try {
window.history.pushState(
'VideoChat', `Room: ${roomName}`, URL
);
} catch (e) {
console.warn("Push history state failed with parameters:",
'VideoChat', `Room: ${roomName}`, URL, e);
return e;
}
return null;
}
/**
* Builds and returns the room name.
@@ -33,6 +52,9 @@ function buildRoomName () {
if(!roomName) {
let word = RoomnameGenerator.generateRoomWithoutSeparator();
roomName = word.toLowerCase();
let historyURL = window.location.href + word;
//Trying to push state with current URL + roomName
pushHistoryState(word, historyURL);
}
return roomName;
@@ -63,10 +85,24 @@ const APP = {
require("./modules/keyboardshortcut/keyboardshortcut");
this.translation = require("./modules/translation/translation");
this.configFetch = require("./modules/config/HttpConfigFetch");
this.tokenData = getTokenData();
}
};
/**
* If JWT token data it will be used for local user settings
*/
function setTokenData() {
let localUser = APP.tokenData.caller;
if(localUser) {
APP.settings.setEmail((localUser.getEmail() || "").trim());
APP.settings.setAvatarUrl((localUser.getAvatarUrl() || "").trim());
APP.settings.setDisplayName((localUser.getName() || "").trim());
}
}
function init() {
setTokenData();
var isUIReady = APP.UI.start();
if (isUIReady) {
APP.conference.init({roomName: buildRoomName()}).then(function () {
@@ -79,6 +115,8 @@ function init() {
APP.keyboardshortcut.init();
}).catch(function (err) {
APP.UI.hideRingOverLay();
APP.API.notifyConferenceLeft(APP.conference.roomName);
console.error(err);
});
}
@@ -130,7 +168,7 @@ $(document).ready(function () {
APP.translation.init(settings.getLanguage());
APP.API.init();
APP.API.init(APP.tokenData.externalAPISettings);
obtainConfigAndInit();
});

View File

@@ -12,6 +12,8 @@ import Recorder from './modules/recorder/Recorder';
import CQEvents from './service/connectionquality/CQEvents';
import UIEvents from './service/UI/UIEvents';
import mediaDeviceHelper from './modules/devices/mediaDeviceHelper';
const ConnectionEvents = JitsiMeetJS.events.connection;
const ConnectionErrors = JitsiMeetJS.errors.connection;
@@ -22,10 +24,21 @@ const TrackEvents = JitsiMeetJS.events.track;
const TrackErrors = JitsiMeetJS.errors.track;
let room, connection, localAudio, localVideo, roomLocker;
let currentAudioInputDevices, currentVideoInputDevices;
import {VIDEO_CONTAINER_TYPE} from "./modules/UI/videolayout/LargeVideo";
/**
* Known custom conference commands.
*/
const commands = {
CONNECTION_QUALITY: "stats",
EMAIL: "email",
AVATAR_URL: "avatar-url",
ETHERPAD: "etherpad",
SHARED_VIDEO: "shared-video",
CUSTOM_ROLE: "custom-role"
};
/**
* Open Connection. When authentication failed it shows auth dialog.
* @param roomName the room name to use
@@ -44,17 +57,65 @@ function connect(roomName) {
}
/**
* Share email with other users.
* @param emailCommand the email command
* @param {string} email new email
* Creates local media tracks and connects to room. Will show error
* dialogs in case if accessing local microphone and/or camera failed. Will
* show guidance overlay for users on how to give access to camera and/or
* microphone,
* @param {string} roomName
* @returns {Promise.<JitsiLocalTrack[], JitsiConnection>}
*/
function sendEmail (emailCommand, email) {
room.sendCommand(emailCommand, {
value: email,
attributes: {
id: room.myUserId()
}
});
function createInitialLocalTracksAndConnect(roomName) {
let audioAndVideoError,
audioOnlyError;
JitsiMeetJS.mediaDevices.addEventListener(
JitsiMeetJS.events.mediaDevices.PERMISSION_PROMPT_IS_SHOWN,
browser => APP.UI.showUserMediaPermissionsGuidanceOverlay(browser));
// First try to retrieve both audio and video.
let tryCreateLocalTracks = createLocalTracks(
{ devices: ['audio', 'video'] }, true)
.catch(err => {
// If failed then try to retrieve only audio.
audioAndVideoError = err;
return createLocalTracks({ devices: ['audio'] }, true);
})
.catch(err => {
// If audio failed too then just return empty array for tracks.
audioOnlyError = err;
return [];
});
return Promise.all([ tryCreateLocalTracks, connect(roomName) ])
.then(([tracks, con]) => {
APP.UI.hideUserMediaPermissionsGuidanceOverlay();
if (audioAndVideoError) {
if (audioOnlyError) {
// If both requests for 'audio' + 'video' and 'audio' only
// failed, we assume that there is some problems with user's
// microphone and show corresponding dialog.
APP.UI.showDeviceErrorDialog(audioOnlyError, null);
} else {
// If request for 'audio' + 'video' failed, but request for
// 'audio' only was OK, we assume that we had problems with
// camera and show corresponding dialog.
APP.UI.showDeviceErrorDialog(null, audioAndVideoError);
}
}
return [tracks, con];
});
}
/**
* Share data to other users.
* @param command the command
* @param {string} value new value
*/
function sendData (command, value) {
room.removeCommand(command);
room.sendCommand(command, {value: value});
}
/**
@@ -144,7 +205,9 @@ function maybeRedirectToWelcomePage() {
* @returns Promise.
*/
function disconnectAndShowFeedback(requestFeedback) {
APP.UI.hideRingOverLay();
connection.disconnect();
APP.API.notifyConferenceLeft(APP.conference.roomName);
if (requestFeedback) {
return APP.UI.requestFeedback();
} else {
@@ -171,42 +234,90 @@ function hangup (requestFeedback = false) {
/**
* Create local tracks of specified types.
* @param {string[]} devices - required track types ('audio', 'video' etc.)
* @param {string|null} [cameraDeviceId] - camera device id, if undefined - one
* from settings will be used
* @param {string|null} [micDeviceId] - microphone device id, if undefined - one
* from settings will be used
* @param {Object} options
* @param {string[]} options.devices - required track types
* ('audio', 'video' etc.)
* @param {string|null} (options.cameraDeviceId) - camera device id, if
* undefined - one from settings will be used
* @param {string|null} (options.micDeviceId) - microphone device id, if
* undefined - one from settings will be used
* @param {boolean} (checkForPermissionPrompt) - if lib-jitsi-meet should check
* for gUM permission prompt
* @returns {Promise<JitsiLocalTrack[]>}
*/
function createLocalTracks (devices, cameraDeviceId, micDeviceId) {
return JitsiMeetJS.createLocalTracks({
// copy array to avoid mutations inside library
devices: devices.slice(0),
resolution: config.resolution,
cameraDeviceId: typeof cameraDeviceId === 'undefined'
|| cameraDeviceId === null
function createLocalTracks (options, checkForPermissionPrompt) {
options || (options = {});
return JitsiMeetJS
.createLocalTracks({
// copy array to avoid mutations inside library
devices: options.devices.slice(0),
resolution: config.resolution,
cameraDeviceId: typeof options.cameraDeviceId === 'undefined' ||
options.cameraDeviceId === null
? APP.settings.getCameraDeviceId()
: cameraDeviceId,
micDeviceId: typeof micDeviceId === 'undefined' || micDeviceId === null
? APP.settings.getMicDeviceId()
: micDeviceId,
// adds any ff fake device settings if any
firefox_fake_device: config.firefox_fake_device
}).catch(function (err) {
console.error('failed to create local tracks', ...devices, err);
return Promise.reject(err);
});
: options.cameraDeviceId,
micDeviceId: typeof options.micDeviceId === 'undefined' ||
options.micDeviceId === null
? APP.settings.getMicDeviceId()
: options.micDeviceId,
// adds any ff fake device settings if any
firefox_fake_device: config.firefox_fake_device
}, checkForPermissionPrompt)
.catch(function (err) {
console.error(
'failed to create local tracks', options.devices, err);
return Promise.reject(err);
});
}
/**
* Changes the email for the local user
* @param email {string} the new email
*/
function changeLocalEmail(email = '') {
email = email.trim();
if (email === APP.settings.getEmail()) {
return;
}
APP.settings.setEmail(email);
APP.UI.setUserEmail(room.myUserId(), email);
sendData(commands.EMAIL, email);
}
/**
* Changes the local avatar url for the local user
* @param avatarUrl {string} the new avatar url
*/
function changeLocalAvatarUrl(avatarUrl = '') {
avatarUrl = avatarUrl.trim();
if (avatarUrl === APP.settings.getAvatarUrl()) {
return;
}
APP.settings.setAvatarUrl(avatarUrl);
APP.UI.setUserAvatarUrl(room.myUserId(), avatarUrl);
sendData(commands.AVATAR_URL, avatarUrl);
}
/**
* Stores lists of current 'audioinput' and 'videoinput' devices
* @param {MediaDeviceInfo[]} devices
* Changes the display name for the local user
* @param nickname {string} the new display name
*/
function setCurrentMediaDevices(devices) {
currentAudioInputDevices = devices.filter(
d => d.kind === 'audioinput');
currentVideoInputDevices = devices.filter(
d => d.kind === 'videoinput');
function changeLocalDisplayName(nickname = '') {
nickname = nickname.trim();
if (nickname === APP.settings.getDisplayName()) {
return;
}
APP.settings.setDisplayName(nickname);
room.setDisplayName(nickname);
APP.UI.changeDisplayName(APP.conference.localId, nickname);
}
class ConferenceConnector {
@@ -227,6 +338,7 @@ class ConferenceConnector {
}
_onConferenceFailed(err, ...params) {
console.error('CONFERENCE FAILED:', err, ...params);
APP.UI.hideRingOverLay();
switch (err) {
// room is locked by the password
case ConferenceErrors.PASSWORD_REQUIRED:
@@ -341,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
@@ -348,7 +468,6 @@ export default {
* @returns {Promise}
*/
init(options) {
let self = this;
this.roomName = options.roomName;
JitsiMeetJS.setLogLevel(JitsiMeetJS.logLevels.TRACE);
@@ -374,253 +493,35 @@ export default {
};
}
return JitsiMeetJS.init(config).then(() => {
return Promise.all([
// try to retrieve audio and video
createLocalTracks(['audio', 'video'])
// if failed then try to retrieve only audio
.catch(() => createLocalTracks(['audio']))
// if audio also failed then just return empty array
.catch(() => []),
connect(options.roomName)
]);
}).then(([tracks, con]) => {
console.log('initialized with %s local tracks', tracks.length);
APP.connection = connection = con;
this._createRoom(tracks);
this.isDesktopSharingEnabled =
JitsiMeetJS.isDesktopSharingEnabled();
return JitsiMeetJS.init(config)
.then(() => createInitialLocalTracksAndConnect(options.roomName))
.then(([tracks, con]) => {
console.log('initialized with %s local tracks', tracks.length);
APP.connection = connection = con;
this._createRoom(tracks);
this.isDesktopSharingEnabled =
JitsiMeetJS.isDesktopSharingEnabled();
// if user didn't give access to mic or camera or doesn't have
// them at all, we disable corresponding toolbar buttons
if (!tracks.find((t) => t.isAudioTrack())) {
APP.UI.disableMicrophoneButton();
}
// if user didn't give access to mic or camera or doesn't have
// them at all, we disable corresponding toolbar buttons
if (!tracks.find((t) => t.isAudioTrack())) {
APP.UI.disableMicrophoneButton();
}
if (!tracks.find((t) => t.isVideoTrack())) {
APP.UI.disableCameraButton();
}
if (!tracks.find((t) => t.isVideoTrack())) {
APP.UI.disableCameraButton();
}
// update list of available devices
if (JitsiMeetJS.mediaDevices.isDeviceListAvailable() &&
JitsiMeetJS.mediaDevices.isDeviceChangeAvailable()) {
JitsiMeetJS.mediaDevices.enumerateDevices(function(devices) {
// Ugly way to synchronize real device IDs with local
// storage and settings menu. This is a workaround until
// getConstraints() method will be implemented in browsers.
if (localAudio) {
localAudio._setRealDeviceIdFromDeviceList(devices);
APP.settings.setMicDeviceId(localAudio.getDeviceId());
}
this._initDeviceList();
if (localVideo) {
localVideo._setRealDeviceIdFromDeviceList(devices);
APP.settings.setCameraDeviceId(
localVideo.getDeviceId());
}
if (config.iAmRecorder)
this.recorder = new Recorder();
setCurrentMediaDevices(devices);
APP.UI.onAvailableDevicesChanged(devices);
// XXX The API will take care of disconnecting from the XMPP
// server (and, thus, leaving the room) on unload.
return new Promise((resolve, reject) => {
(new ConferenceConnector(resolve, reject)).connect();
});
JitsiMeetJS.mediaDevices.addEventListener(
JitsiMeetJS.events.mediaDevices.DEVICE_LIST_CHANGED,
(devices) => {
// Just defer callback until other event callbacks are
// processed.
window.setTimeout(() => {
checkLocalDevicesAfterDeviceListChanged(devices)
.then(() => {
setCurrentMediaDevices(devices);
APP.UI.onAvailableDevicesChanged(devices);
});
}, 0);
});
}
if (config.iAmRecorder)
this.recorder = new Recorder();
// XXX The API will take care of disconnecting from the XMPP server
// (and, thus, leaving the room) on unload.
return new Promise((resolve, reject) => {
(new ConferenceConnector(resolve, reject)).connect();
});
function checkAudioOutputDeviceAfterDeviceListChanged(newDevices) {
if (!JitsiMeetJS.mediaDevices
.isDeviceChangeAvailable('output')) {
return;
}
var selectedAudioOutputDeviceId =
APP.settings.getAudioOutputDeviceId(),
availableAudioOutputDevices = newDevices.filter(d => {
return d.kind === 'audiooutput';
});
if (selectedAudioOutputDeviceId !== 'default' &&
!availableAudioOutputDevices.find(d =>
d.deviceId === selectedAudioOutputDeviceId)) {
APP.settings.setAudioOutputDeviceId('default');
}
}
function checkLocalDevicesAfterDeviceListChanged(newDevices) {
// Event handler can be fire before direct enumerateDevices()
// call, so handle this situation here.
if (!currentAudioInputDevices && !currentVideoInputDevices) {
setCurrentMediaDevices(newDevices);
}
checkAudioOutputDeviceAfterDeviceListChanged(newDevices);
let availableAudioInputDevices = newDevices.filter(
d => d.kind === 'audioinput'),
availableVideoInputDevices = newDevices.filter(
d => d.kind === 'videoinput'),
selectedAudioInputDeviceId = APP.settings.getMicDeviceId(),
selectedVideoInputDeviceId =
APP.settings.getCameraDeviceId(),
selectedAudioInputDevice = availableAudioInputDevices.find(
d => d.deviceId === selectedAudioInputDeviceId),
selectedVideoInputDevice = availableVideoInputDevices.find(
d => d.deviceId === selectedVideoInputDeviceId),
tracksToCreate = [],
micIdToUse = null,
cameraIdToUse = null;
// Here we handle case when no device was initially plugged, but
// then it's connected OR new device was connected when previous
// track has ended.
if (!localAudio || localAudio.disposed || localAudio.isEnded()){
if (availableAudioInputDevices.length
&& availableAudioInputDevices[0].label !== '') {
tracksToCreate.push('audio');
micIdToUse = availableAudioInputDevices[0].deviceId;
} else {
APP.UI.disableMicrophoneButton();
}
}
if ((!localVideo || localVideo.disposed || localVideo.isEnded())
&& !self.isSharingScreen){
if (availableVideoInputDevices.length
&& availableVideoInputDevices[0].label !== '') {
tracksToCreate.push('video');
cameraIdToUse = availableVideoInputDevices[0].deviceId;
} else {
APP.UI.disableCameraButton();
}
}
if (localAudio && !localAudio.disposed && !localAudio.isEnded()
&& selectedAudioInputDevice
&& selectedAudioInputDeviceId !== localAudio.getDeviceId()
&& tracksToCreate.indexOf('audio') === -1) {
tracksToCreate.push('audio');
micIdToUse = selectedAudioInputDeviceId;
}
if (localVideo && !localVideo.disposed && !localVideo.isEnded()
&& selectedVideoInputDevice
&& selectedVideoInputDeviceId !== localVideo.getDeviceId()
&& tracksToCreate.indexOf('video') === -1
&& !self.isSharingScreen) {
tracksToCreate.push('video');
cameraIdToUse = selectedVideoInputDeviceId;
}
if (tracksToCreate.length) {
return createNewTracks(
tracksToCreate, cameraIdToUse, micIdToUse);
} else {
return Promise.resolve();
}
function createNewTracks(type, cameraDeviceId, micDeviceId) {
return createLocalTracks(type, cameraDeviceId, micDeviceId)
.then(onTracksCreated)
.catch(() => {
// if we tried to create both audio and video tracks
// at once and failed, let's try again only with
// audio. Such situation may happen in case if we
// granted access only to microphone, but not to
// camera.
if (type.indexOf('audio') !== -1
&& type.indexOf('video') !== -1) {
return createLocalTracks(['audio'], null,
micDeviceId);
}
})
.then(onTracksCreated)
.catch(() => {
// if we tried to create both audio and video tracks
// at once and failed, let's try again only with
// video. Such situation may happen in case if we
// granted access only to camera, but not to
// microphone.
if (type.indexOf('audio') !== -1
&& type.indexOf('video') !== -1) {
return createLocalTracks(['video'],
cameraDeviceId,
null);
}
})
.then(onTracksCreated)
.catch(() => {
// can't do anything in this case, so just ignore;
});
}
function onTracksCreated(tracks) {
return Promise.all((tracks || []).map(track => {
if (track.isAudioTrack()) {
let audioWasMuted = self.audioMuted;
return self.useAudioStream(track).then(() => {
console.log('switched local audio');
// If we plugged-in new device (and switched to
// it), but video was muted before, or we
// unplugged current device and selected new
// one, then mute new video track.
if (audioWasMuted ||
currentAudioInputDevices.length >
availableAudioInputDevices.length) {
muteLocalAudio(true);
}
});
} else if (track.isVideoTrack()) {
let videoWasMuted = self.videoMuted;
return self.useVideoStream(track).then(() => {
console.log('switched local video');
// TODO: maybe make video large if we
// are not in conference yet
// If we plugged-in new device (and switched to
// it), but video was muted before, or we
// unplugged current device and selected new
// one, then mute new video track.
if (videoWasMuted ||
(currentVideoInputDevices.length >
availableVideoInputDevices.length)) {
muteLocalVideo(true);
}
});
} else {
console.error("Ignored not an audio nor a "
+ "video track: ", track);
return Promise.resolve();
}
}));
}
}
});
},
/**
@@ -789,13 +690,7 @@ export default {
/**
* Known custom conference commands.
*/
defaults: {
CONNECTION_QUALITY: "stats",
EMAIL: "email",
ETHERPAD: "etherpad",
SHARED_VIDEO: "shared-video",
CUSTOM_ROLE: "custom-role"
},
defaults: commands,
/**
* Receives notifications from other participants about commands aka
* custom events (sent by sendCommand or sendCommandOnce methods).
@@ -834,21 +729,16 @@ export default {
room = connection.initJitsiConference(APP.conference.roomName,
this._getConferenceOptions());
this.localId = room.myUserId();
localTracks.forEach((track) => {
if (track.isAudioTrack()) {
this.useAudioStream(track);
} else if (track.isVideoTrack()) {
this.useVideoStream(track);
} else {
console.error(
"Ignored not an audio nor a video track: ", track);
}
});
this._setLocalAudioVideoStreams(localTracks);
roomLocker = createRoomLocker(room);
this._room = room; // FIXME do not use this
let email = APP.settings.getEmail();
email && sendEmail(this.commands.defaults.EMAIL, email);
email && sendData(this.commands.defaults.EMAIL, email);
let avatarUrl = APP.settings.getAvatarUrl();
avatarUrl && sendData(this.commands.defaults.AVATAR_URL,
avatarUrl);
let nick = APP.settings.getDisplayName();
if (config.useNicks && !nick) {
@@ -860,6 +750,26 @@ export default {
this._setupListeners();
},
/**
* Sets local video and audio streams.
* @param {JitsiLocalTrack[]} tracks=[]
* @returns {Promise[]}
* @private
*/
_setLocalAudioVideoStreams(tracks = []) {
return tracks.map(track => {
if (track.isAudioTrack()) {
return this.useAudioStream(track);
} else if (track.isVideoTrack()) {
return this.useVideoStream(track);
} else {
console.error(
"Ignored not an audio nor a video track: ", track);
return Promise.resolve();
}
});
},
_getConferenceOptions() {
let options = config;
if(config.enableRecording && !config.recordingType) {
@@ -895,13 +805,13 @@ export default {
this.isSharingScreen = stream.videoType === 'desktop';
APP.UI.addLocalStream(stream);
stream.videoType === 'camera' && APP.UI.enableCameraButton();
} else {
this.videoMuted = false;
this.isSharingScreen = false;
}
stream.videoType === 'camera' && APP.UI.enableCameraButton();
APP.UI.setVideoMuted(this.localId, this.videoMuted);
APP.UI.updateDesktopSharingButtons();
@@ -955,7 +865,7 @@ export default {
this.videoSwitchInProgress = true;
if (shareScreen) {
createLocalTracks(['desktop']).then(([stream]) => {
createLocalTracks({ devices: ['desktop'] }).then(([stream]) => {
stream.on(
TrackEvents.LOCAL_TRACK_STOPPED,
() => {
@@ -976,12 +886,13 @@ export default {
this.videoSwitchInProgress = false;
this.toggleScreenSharing(false);
if(err === TrackErrors.CHROME_EXTENSION_USER_CANCELED)
if (err.name === TrackErrors.CHROME_EXTENSION_USER_CANCELED) {
return;
}
console.error('failed to share local desktop', err);
if (err === TrackErrors.FIREFOX_EXTENSION_NEEDED) {
if (err.name === TrackErrors.FIREFOX_EXTENSION_NEEDED) {
APP.UI.showExtensionRequiredDialog(
config.desktopSharingFirefoxExtensionURL
);
@@ -989,21 +900,29 @@ export default {
}
// Handling:
// TrackErrors.PERMISSION_DENIED
// TrackErrors.CHROME_EXTENSION_INSTALLATION_ERROR
// TrackErrors.GENERAL
// and any other
let dialogTxt = APP.translation
.generateTranslationHTML("dialog.failtoinstall");
let dialogTitle = APP.translation
.generateTranslationHTML("dialog.error");
APP.UI.messageHandler.openDialog(
dialogTitle,
dialogTxt,
false
);
let dialogTxt;
let dialogTitle;
if (err.name === TrackErrors.PERMISSION_DENIED) {
dialogTxt = APP.translation.generateTranslationHTML(
"dialog.screenSharingPermissionDeniedError");
dialogTitle = APP.translation.generateTranslationHTML(
"dialog.error");
} else {
dialogTxt = APP.translation.generateTranslationHTML(
"dialog.failtoinstall");
dialogTitle = APP.translation.generateTranslationHTML(
"dialog.permissionDenied");
}
APP.UI.messageHandler.openDialog(dialogTitle, dialogTxt, false);
});
} else {
createLocalTracks(['video']).then(
createLocalTracks({ devices: ['video'] }).then(
([stream]) => this.useVideoStream(stream)
).then(() => {
this.videoSwitchInProgress = false;
@@ -1022,6 +941,7 @@ export default {
// add local streams when joined to the conference
room.on(ConferenceEvents.CONFERENCE_JOINED, () => {
APP.UI.mucJoined();
APP.API.notifyConferenceJoined(APP.conference.roomName);
});
room.on(
@@ -1108,7 +1028,8 @@ export default {
if(config.debug)
{
this.audioLevelsMap[id] = lvl;
console.log("AudioLevel:" + id + "/" + lvl);
if(config.debugAudioLevels)
console.log("AudioLevel:" + id + "/" + lvl);
}
APP.UI.setAudioLevel(id, lvl);
@@ -1127,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);
});
@@ -1149,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);
@@ -1225,33 +1163,20 @@ export default {
APP.UI.initEtherpad(value);
});
APP.UI.addListener(UIEvents.EMAIL_CHANGED, (email = '') => {
email = email.trim();
if (email === APP.settings.getEmail()) {
return;
}
APP.settings.setEmail(email);
APP.UI.setUserAvatar(room.myUserId(), email);
sendEmail(this.commands.defaults.EMAIL, email);
});
room.addCommandListener(this.commands.defaults.EMAIL, (data) => {
APP.UI.setUserAvatar(data.attributes.id, data.value);
APP.UI.addListener(UIEvents.EMAIL_CHANGED, changeLocalEmail);
room.addCommandListener(this.commands.defaults.EMAIL, (data, from) => {
APP.UI.setUserEmail(from, data.value);
});
APP.UI.addListener(UIEvents.NICKNAME_CHANGED, (nickname = '') => {
nickname = nickname.trim();
APP.UI.addListener(UIEvents.AVATAR_URL_CHANGED, changeLocalAvatarUrl);
if (nickname === APP.settings.getDisplayName()) {
return;
}
APP.settings.setDisplayName(nickname);
room.setDisplayName(nickname);
APP.UI.changeDisplayName(APP.conference.localId, nickname);
room.addCommandListener(this.commands.defaults.AVATAR_URL,
(data, from) => {
APP.UI.setUserAvatarUrl(from, data.value);
});
APP.UI.addListener(UIEvents.NICKNAME_CHANGED, changeLocalDisplayName);
APP.UI.addListener(UIEvents.START_MUTED_CHANGED,
(startAudioMuted, startVideoMuted) => {
room.setStartMutedPolicy({
@@ -1353,10 +1278,19 @@ export default {
APP.UI.addListener(
UIEvents.VIDEO_DEVICE_CHANGED,
(cameraDeviceId) => {
APP.settings.setCameraDeviceId(cameraDeviceId);
createLocalTracks(['video']).then(([stream]) => {
createLocalTracks({
devices: ['video'],
cameraDeviceId: cameraDeviceId,
micDeviceId: null
})
.then(([stream]) => {
this.useVideoStream(stream);
console.log('switched local video device');
APP.settings.setCameraDeviceId(cameraDeviceId);
})
.catch((err) => {
APP.UI.showDeviceErrorDialog(null, err);
APP.UI.setSelectedCameraFromSettings();
});
}
);
@@ -1364,10 +1298,19 @@ export default {
APP.UI.addListener(
UIEvents.AUDIO_DEVICE_CHANGED,
(micDeviceId) => {
APP.settings.setMicDeviceId(micDeviceId);
createLocalTracks(['audio']).then(([stream]) => {
createLocalTracks({
devices: ['audio'],
cameraDeviceId: null,
micDeviceId: micDeviceId
})
.then(([stream]) => {
this.useAudioStream(stream);
console.log('switched local audio device');
APP.settings.setMicDeviceId(micDeviceId);
})
.catch((err) => {
APP.UI.showDeviceErrorDialog(err, null);
APP.UI.setSelectedMicFromSettings();
});
}
);
@@ -1381,6 +1324,7 @@ export default {
console.warn('Failed to change audio output device. ' +
'Default or previously set audio output device ' +
'will be used instead.', err);
APP.UI.setSelectedAudioOutputFromSettings();
});
}
);
@@ -1436,11 +1380,137 @@ export default {
});
},
/**
* Adss any room listener.
* @param eventName one of the ConferenceEvents
* @param callBack the function to be called when the event occurs
*/
addConferenceListener(eventName, callBack) {
* Adds any room listener.
* @param eventName one of the ConferenceEvents
* @param callBack the function to be called when the event occurs
*/
addConferenceListener(eventName, callBack) {
room.on(eventName, callBack);
},
/**
* Inits list of current devices and event listener for device change.
* @private
*/
_initDeviceList() {
if (JitsiMeetJS.mediaDevices.isDeviceListAvailable() &&
JitsiMeetJS.mediaDevices.isDeviceChangeAvailable()) {
JitsiMeetJS.mediaDevices.enumerateDevices(devices => {
// Ugly way to synchronize real device IDs with local
// storage and settings menu. This is a workaround until
// getConstraints() method will be implemented in browsers.
if (localAudio) {
localAudio._setRealDeviceIdFromDeviceList(devices);
APP.settings.setMicDeviceId(localAudio.getDeviceId());
}
if (localVideo) {
localVideo._setRealDeviceIdFromDeviceList(devices);
APP.settings.setCameraDeviceId(localVideo.getDeviceId());
}
mediaDeviceHelper.setCurrentMediaDevices(devices);
APP.UI.onAvailableDevicesChanged(devices);
});
JitsiMeetJS.mediaDevices.addEventListener(
JitsiMeetJS.events.mediaDevices.DEVICE_LIST_CHANGED,
(devices) =>
window.setTimeout(
() => this._onDeviceListChanged(devices), 0));
}
},
/**
* Event listener for JitsiMediaDevicesEvents.DEVICE_LIST_CHANGED to
* handle change of available media devices.
* @private
* @param {MediaDeviceInfo[]} devices
* @returns {Promise}
*/
_onDeviceListChanged(devices) {
let currentDevices = mediaDeviceHelper.getCurrentMediaDevices();
// Event handler can be fired before direct
// enumerateDevices() call, so handle this situation here.
if (!currentDevices.audioinput &&
!currentDevices.videoinput &&
!currentDevices.audiooutput) {
mediaDeviceHelper.setCurrentMediaDevices(devices);
currentDevices = mediaDeviceHelper.getCurrentMediaDevices();
}
let newDevices =
mediaDeviceHelper.getNewMediaDevicesAfterDeviceListChanged(
devices, this.isSharingScreen, localVideo, localAudio);
let promises = [];
let audioWasMuted = this.audioMuted;
let videoWasMuted = this.videoMuted;
let availableAudioInputDevices =
mediaDeviceHelper.getDevicesFromListByKind(devices, 'audioinput');
let availableVideoInputDevices =
mediaDeviceHelper.getDevicesFromListByKind(devices, 'videoinput');
if (typeof newDevices.audiooutput !== 'undefined') {
// Just ignore any errors in catch block.
promises.push(APP.settings
.setAudioOutputDeviceId(newDevices.audiooutput)
.catch());
}
promises.push(
mediaDeviceHelper.createLocalTracksAfterDeviceListChanged(
createLocalTracks,
newDevices.videoinput,
newDevices.audioinput)
.then(tracks =>
Promise.all(this._setLocalAudioVideoStreams(tracks)))
.then(() => {
// If audio was muted before, or we unplugged current device
// and selected new one, then mute new audio track.
if (audioWasMuted ||
currentDevices.audioinput.length >
availableAudioInputDevices.length) {
muteLocalAudio(true);
}
// If video was muted before, or we unplugged current device
// and selected new one, then mute new video track.
if (videoWasMuted ||
currentDevices.videoinput.length >
availableVideoInputDevices.length) {
muteLocalVideo(true);
}
}));
return Promise.all(promises)
.then(() => {
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

@@ -16,12 +16,13 @@
* Executes createConnectionExternally function.
*/
(function () {
var params = getConfigParamsFromUrl();
var hashParams = getConfigParamsFromUrl("hash", true);
var searchParams = getConfigParamsFromUrl("search", true);
//Url params have higher proirity than config params
var url = config.externalConnectUrl;
if(params.hasOwnProperty('config.externalConnectUrl'))
url = params["config.externalConnectUrl"];
if(hashParams.hasOwnProperty('config.externalConnectUrl'))
url = hashParams["config.externalConnectUrl"];
/**
* Check if connect from connection.js was executed and executes the handler
@@ -57,7 +58,8 @@
url += "?room=" + room_name;
var token = params["config.token"] || config.token;
var token = hashParams["config.token"] || config.token ||
searchParams.jwt;
if(token)
url += "&token=" + token;

View File

@@ -8,11 +8,15 @@
text-align: left;
padding: 7px 10px;
margin: 2px;
color: #00ccff;
color: #21B9FC;
font-size: 11pt;
border-bottom: 1px solid #676767;
}
#contactlist>div.title>span {
margin-left: 5px;
}
#contactlist>ul#contacts {
position: absolute;
top: 31px;

View File

@@ -1,3 +1,15 @@
@font-face {
font-family: 'open_sanslight';
src: url('../fonts/OpenSans-Light-webfont.eot');
src: url('../fonts/OpenSans-Light-webfont.eot?#iefix') format('embedded-opentype'),
url('../fonts/OpenSans-Light-webfont.woff') format('woff'),
url('../fonts/OpenSans-Light-webfont.ttf') format('truetype'),
url('../fonts/OpenSans-Light-webfont.svg#open_sanslight') format('svg');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'jitsi';
src:url('../fonts/jitsi.eot?94d075');

View File

@@ -86,12 +86,12 @@
.jitsipopover_blue
{
color: #06a5df;
color: #21B9FC;
}
.jitsipopover_showmore
{
background-color: #06a5df;
background-color: #21B9FC;
color: #ffffff;
cursor: pointer;
border-radius: 3px;

View File

@@ -0,0 +1,19 @@
#keyboard-shortcuts {
display: none;
position: absolute;
bottom: 20px;
left: 20px;
overflow: hidden;
padding: 20px;
z-index: 1;
border-radius: 15px;
background-attachment: scroll;
background-size: auto auto;
color: rgba(255, 255, 255, .8);
background-color: rgba(0, 0, 0, .8);
}
#keyboard-shortcuts .item-action {
color: #209EFF;
font-size: 14pt;
}

View File

@@ -1,6 +1,6 @@
/*Initialize*/
ul.loginmenu {
font-family:'Helvetica Neue', Helvetica, sans-serif;
font-family: inherit;
display:none;
position: absolute;
margin: 0;

View File

@@ -7,12 +7,19 @@ html, body{
margin:0px;
height:100%;
color: #424242;
font-family:'Helvetica Neue', Helvetica, sans-serif;
font-size: 14px;
font-weight: 400;
background: #000000;
overflow: hidden;
}
html, body, input, textarea, keygen, select, button {
font-family: 'open_sanslight',
'Helvetica Neue',
Helvetica,
sans-serif !important;
}
.right-panel {
display:none;
position:absolute;
@@ -33,6 +40,10 @@ html, body{
display:none;
}
#header_container {
z-index: 1014;
}
.toolbar_span {
display: inline-block;
position: relative;
@@ -54,7 +65,7 @@ html, body{
text-align: center;
text-shadow: 0 1px 0 rgba(255,255,255,.3), 0 -1px 0 rgba(0,0,0,.6);
z-index: 1;
font-size: 1.22em !important;
font-size: 1.44em !important;
vertical-align: middle;
}
@@ -78,18 +89,18 @@ html, body{
}
/*#ffde00*/
#toolbar_button_chat.active, #contactListButton.glowing, #chatBottomButton.glowing {
-webkit-text-shadow: -1px 0 10px #00ccff,
0 1px 10px #00ccff,
1px 0 10px #00ccff,
0 -1px 10px #00ccff;
-moz-text-shadow: 1px 0 10px #00ccff,
0 1px 10px #00ccff,
1px 0 10px #00ccff,
0 -1px 10px #00ccff;
text-shadow: -1px 0 10px #00ccff,
0 1px 10px #00ccff,
1px 0 10px #00ccff,
0 -1px 10px #00ccff;
-webkit-text-shadow: -1px 0 10px #21B9FC,
0 1px 10px #21B9FC,
1px 0 10px #21B9FC,
0 -1px 10px #21B9FC;
-moz-text-shadow: 1px 0 10px #21B9FC,
0 1px 10px #21B9FC,
1px 0 10px #21B9FC,
0 -1px 10px #21B9FC;
text-shadow: -1px 0 10px #21B9FC,
0 1px 10px #21B9FC,
1px 0 10px #21B9FC,
0 -1px 10px #21B9FC;
}
#toolbar_button_hangup {
@@ -112,7 +123,7 @@ html, body{
}
#contactListButton.active #numberOfParticipants {
color: #00ccff;
color: #21B9FC;
}
#toolbar {
@@ -361,16 +372,11 @@ div.feedbackButton:hover {
}
.connected {
color: forestgreen;
color: #21B9FC;
font-size: 12px;
}
.disconnected {
color: darkred;
font-size: 12px;
}
.lastN {
.lastN, .disconnected {
color: #a3a3a3;
font-size: 12px;
}

66
css/overlay.css Normal file
View File

@@ -0,0 +1,66 @@
.overlay {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
z-index: 1013;
background: #21B9FC; /* Old browsers */
opacity: 0.75;
display: block;
}
.overlay_transparent {
background: rgba(22, 185, 252, .9);
}
.overlay_container {
width: 100%;
height: 100%;
position: fixed;
z-index: 1013;
}
.overlay_content {
color: #fff;
font-weight: normal;
font-size: 20px;
text-align: center;
width: 400px;
height: 250px;
top: 50%;
left: 50%;
position:absolute;
margin-top: -125px;
margin-left: -200px;
}
.overlay_avatar {
width: 200px;
height: 200px;
position: relative;
border-radius: 100px;
z-index: 1013;
float: left;
margin-left: 100px;
}
.overlay_text {
position: relative;
width: 400px;
z-index: 1013;
margin-top: 50px;
float: left;
}
.overlay_text_small {
font-size: 18px;
}
.overlay_icon {
position: relative;
z-index: 1013;
float: none;
font-size: 100px;
}

View File

@@ -2,7 +2,7 @@
position: absolute;
top: 0;
left: 0;
z-index: 1010;
z-index: 1015;
display: none;
max-width: 300px;
min-width: 100px;
@@ -121,4 +121,4 @@
border-right-width: 0;
border-left-color: #ffffff;
bottom: -10px;
}
}

View File

@@ -1,7 +1,7 @@
#settingsmenu {
display: none;
background: black;
color: #00ccff;
color: #21B9FC;
overflow-y: auto;
}
@@ -16,6 +16,18 @@
color: #a7a7a7;
}
#settingsmenu>div.title {
text-align: left;
padding: 7px 10px;
margin: 2px;
font-size: 11pt;
border-bottom: 1px solid #676767;
}
#settingsmenu>div.title>span {
margin-left: 5px;
}
#settingsmenu .arrow-up {
width: 0;
height: 0;
@@ -35,10 +47,6 @@
position: relative;
}
#settingsmenu .icon-settings {
padding: 34px;
}
#languages_selectbox {
height: 40px;
cursor: pointer;

View File

@@ -84,7 +84,7 @@ button.toast-close-button {
}
#toast-container {
position: fixed;
z-index: 999999;
z-index: 1012;
/*overrides*/
}
@@ -95,7 +95,7 @@ button.toast-close-button {
}
#toast-container > div {
margin: 0 0 6px;
padding: 15px 15px 15px 50px;
padding: 15px 15px 15px 15px;
width: 300px;
-moz-border-radius: 3px 3px 3px 3px;
-webkit-border-radius: 3px 3px 3px 3px;
@@ -119,21 +119,17 @@ button.toast-close-button {
filter: alpha(opacity=100);
cursor: pointer;
}
#toast-container > .toast-info {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGwSURBVEhLtZa9SgNBEMc9sUxxRcoUKSzSWIhXpFMhhYWFhaBg4yPYiWCXZxBLERsLRS3EQkEfwCKdjWJAwSKCgoKCcudv4O5YLrt7EzgXhiU3/4+b2ckmwVjJSpKkQ6wAi4gwhT+z3wRBcEz0yjSseUTrcRyfsHsXmD0AmbHOC9Ii8VImnuXBPglHpQ5wwSVM7sNnTG7Za4JwDdCjxyAiH3nyA2mtaTJufiDZ5dCaqlItILh1NHatfN5skvjx9Z38m69CgzuXmZgVrPIGE763Jx9qKsRozWYw6xOHdER+nn2KkO+Bb+UV5CBN6WC6QtBgbRVozrahAbmm6HtUsgtPC19tFdxXZYBOfkbmFJ1VaHA1VAHjd0pp70oTZzvR+EVrx2Ygfdsq6eu55BHYR8hlcki+n+kERUFG8BrA0BwjeAv2M8WLQBtcy+SD6fNsmnB3AlBLrgTtVW1c2QN4bVWLATaIS60J2Du5y1TiJgjSBvFVZgTmwCU+dAZFoPxGEEs8nyHC9Bwe2GvEJv2WXZb0vjdyFT4Cxk3e/kIqlOGoVLwwPevpYHT+00T+hWwXDf4AJAOUqWcDhbwAAAAASUVORK5CYII=") !important;
}
#toast-container > .toast-error {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHOSURBVEhLrZa/SgNBEMZzh0WKCClSCKaIYOED+AAKeQQLG8HWztLCImBrYadgIdY+gIKNYkBFSwu7CAoqCgkkoGBI/E28PdbLZmeDLgzZzcx83/zZ2SSXC1j9fr+I1Hq93g2yxH4iwM1vkoBWAdxCmpzTxfkN2RcyZNaHFIkSo10+8kgxkXIURV5HGxTmFuc75B2RfQkpxHG8aAgaAFa0tAHqYFfQ7Iwe2yhODk8+J4C7yAoRTWI3w/4klGRgR4lO7Rpn9+gvMyWp+uxFh8+H+ARlgN1nJuJuQAYvNkEnwGFck18Er4q3egEc/oO+mhLdKgRyhdNFiacC0rlOCbhNVz4H9FnAYgDBvU3QIioZlJFLJtsoHYRDfiZoUyIxqCtRpVlANq0EU4dApjrtgezPFad5S19Wgjkc0hNVnuF4HjVA6C7QrSIbylB+oZe3aHgBsqlNqKYH48jXyJKMuAbiyVJ8KzaB3eRc0pg9VwQ4niFryI68qiOi3AbjwdsfnAtk0bCjTLJKr6mrD9g8iq/S/B81hguOMlQTnVyG40wAcjnmgsCNESDrjme7wfftP4P7SP4N3CJZdvzoNyGq2c/HWOXJGsvVg+RA/k2MC/wN6I2YA2Pt8GkAAAAASUVORK5CYII=") !important;
}
#toast-container > .toast-success {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADsSURBVEhLY2AYBfQMgf///3P8+/evAIgvA/FsIF+BavYDDWMBGroaSMMBiE8VC7AZDrIFaMFnii3AZTjUgsUUWUDA8OdAH6iQbQEhw4HyGsPEcKBXBIC4ARhex4G4BsjmweU1soIFaGg/WtoFZRIZdEvIMhxkCCjXIVsATV6gFGACs4Rsw0EGgIIH3QJYJgHSARQZDrWAB+jawzgs+Q2UO49D7jnRSRGoEFRILcdmEMWGI0cm0JJ2QpYA1RDvcmzJEWhABhD/pqrL0S0CWuABKgnRki9lLseS7g2AlqwHWQSKH4oKLrILpRGhEQCw2LiRUIa4lwAAAABJRU5ErkJggg==") !important;
}
#toast-container > .toast-warning {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGYSURBVEhL5ZSvTsNQFMbXZGICMYGYmJhAQIJAICYQPAACiSDB8AiICQQJT4CqQEwgJvYASAQCiZiYmJhAIBATCARJy+9rTsldd8sKu1M0+dLb057v6/lbq/2rK0mS/TRNj9cWNAKPYIJII7gIxCcQ51cvqID+GIEX8ASG4B1bK5gIZFeQfoJdEXOfgX4QAQg7kH2A65yQ87lyxb27sggkAzAuFhbbg1K2kgCkB1bVwyIR9m2L7PRPIhDUIXgGtyKw575yz3lTNs6X4JXnjV+LKM/m3MydnTbtOKIjtz6VhCBq4vSm3ncdrD2lk0VgUXSVKjVDJXJzijW1RQdsU7F77He8u68koNZTz8Oz5yGa6J3H3lZ0xYgXBK2QymlWWA+RWnYhskLBv2vmE+hBMCtbA7KX5drWyRT/2JsqZ2IvfB9Y4bWDNMFbJRFmC9E74SoS0CqulwjkC0+5bpcV1CZ8NMej4pjy0U+doDQsGyo1hzVJttIjhQ7GnBtRFN1UarUlH8F3xict+HY07rEzoUGPlWcjRFRr4/gChZgc3ZL2d8oAAAAASUVORK5CYII=") !important;
#toast-container .toast-info,
#toast-container .toast-success,
#toast-container .toast-error,
#toast-container .toast-warning
{
padding: 10px 10px 10px 10px !important;
}
#toast-container.toast-top-full-width > div,
#toast-container.toast-bottom-full-width > div {
width: 96%;
width: 100%;
margin: auto;
}
.toast {
@@ -154,7 +150,7 @@ button.toast-close-button {
/*Responsive Design*/
@media all and (max-width: 240px) {
#toast-container > div {
padding: 8px 8px 8px 50px;
padding: 8px 8px 8px 8px;
width: 11em;
}
#toast-container .toast-close-button {
@@ -164,7 +160,7 @@ button.toast-close-button {
}
@media all and (min-width: 241px) and (max-width: 480px) {
#toast-container > div {
padding: 8px 8px 8px 50px;
padding: 8px 8px 8px 8px;
width: 18em;
}
#toast-container .toast-close-button {
@@ -174,7 +170,7 @@ button.toast-close-button {
}
@media all and (min-width: 481px) and (max-width: 768px) {
#toast-container > div {
padding: 15px 15px 15px 50px;
padding: 15px 15px 15px 15px;
width: 25em;
}
}
}

View File

@@ -3,7 +3,7 @@ body {
height:100%;
background-color: white;
color: #424242;
font-family:Helvetica,'YanoneKaffeesatzLight',Verdana,Tahoma,Arial;
font-family: 'open_sanslight', 'Helvetica Neue', Helvetica, sans-serif;
font-size: 28px;
margin:0;
padding:0;

View File

@@ -310,7 +310,7 @@
z-index: 3;
}
.videocontainer>span.dominantspeakerindicator {
.videocontainer>span.indicator {
bottom: 0px;
left: 0px;
width: 25px;
@@ -318,7 +318,7 @@
z-index: 3;
text-align: center;
border-radius: 50%;
background: #0cf;
background: #21B9FC;
margin: 5px;
display: inline-block;
position: absolute;
@@ -327,7 +327,7 @@
border: 0px;
}
#speakerindicatoricon {
#indicatoricon {
padding-top: 5px;
}
@@ -468,7 +468,7 @@
position: absolute;
width: 100%;
top:50%;
z-index: 10000;
z-index: 1011;
font-weight: 600;
font-size: 14px;
text-align: center;
@@ -488,7 +488,7 @@
background: rgba(0,0,0,.5);
padding: 10px;
color: rgba(255,255,255,.5);
z-index: 10000;
z-index: 1011;
}
.centeredVideoLabel {
@@ -506,7 +506,7 @@
margin-left: auto;
background: rgba(0,0,0,.5);
color: #FFF;
z-index: 10000;
z-index: 1011;
border-radius: 2px;
-webkit-transition: all 2s 2s linear;
transition: all 2s 2s linear;
@@ -522,4 +522,4 @@
}
.hidden {
}
}

View File

@@ -21,7 +21,6 @@
-moz-user-select: none;
background-repeat: no-repeat;
font-weight: 500;
font-family: Helvetica;
font-size: 16px;
color: #acacac;
z-index: 2;
@@ -35,7 +34,6 @@
-moz-user-select: none;
background-repeat: no-repeat;
font-weight: 500;
font-family: Helvetica;
font-size: 16px;
color: #acacac;
z-index: 2;
@@ -56,21 +54,21 @@
#domain_name
{
float: left;
padding: 20px 0px 10px 20px;
height: 55px;
line-height: 55px;
font-size: 18px;
font-weight: 500;
font-family: Helvetica;
padding-left: 20px;
}
#enter_room_field {
font-size: 15px;
padding: 15px 0px 10px 10px;
border: none;
-webkit-appearance: none;
width: 228px;
height: 55px;
line-height: 55px;
font-weight: 500;
font-family: Helvetica;
box-shadow: none;
float: left;
background-color: #FFFFFF;
@@ -81,7 +79,7 @@
#enter_room_button {
width: 73px;
height: 45px;
background-color: #16a8fe;
background-color: #21B9FC;
moz-border-radius: 1px;
-webkit-border-radius: 1px;
color: #ffffff;
@@ -89,7 +87,6 @@
border: none;
margin-top: 5px;
font-size: 19px;
font-family: Helvetica;
padding-top: 6px;
outline: none;
float:left;
@@ -136,7 +133,6 @@
width: 885px;
height: 100px;
color: #ffffff;
font-family: Helvetica;
font-size: 24px;
text-align: center;
margin: 0px auto 0px auto;
@@ -171,7 +167,6 @@
background-repeat: no-repeat;
width: 169px;
height: 169px;
font-family: Helvetica;
color: #ffffff;
font-size: 22px;
/*font-weight: bold;*/
@@ -183,7 +178,6 @@
.feature_description
{
width: 190px;
font-family: Helvetica;
color: #ffffff;
font-size: 16px;
padding-top: 30px;

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

@@ -67,8 +67,8 @@ case "$1" in
sed -i 's/ --modules_enabled = { "token_verification" }/ modules_enabled = { "token_verification" }/g' $PROSODY_HOST_CONFIG
# Install luajwt
if ! luarocks install luajwt; then
echo "Failed to install luajwt - try installing it manually"
if ! luarocks install jwt; then
echo "Failed to install jwt - try installing it manually"
fi
if [ -x "/etc/init.d/prosody" ]; then
@@ -85,7 +85,7 @@ case "$1" in
else
echo "Prosody config not found at $PROSODY_HOST_CONFIG - unable to auto-configure token authentication"
fi
;;
abort-upgrade|abort-remove|abort-deconfigure)

View File

@@ -90,6 +90,7 @@ case "$1" in
echo "org.jitsi.videobridge.rest.jetty.ResourceHandler.alias./config.js=/etc/jitsi/meet/$JVB_HOSTNAME-config.js" >> $JVB_CONFIG
echo "org.jitsi.videobridge.rest.jetty.RewriteHandler.regex=^/([a-zA-Z0-9]+)$" >> $JVB_CONFIG
echo "org.jitsi.videobridge.rest.jetty.RewriteHandler.replacement=/" >> $JVB_CONFIG
echo "org.jitsi.videobridge.rest.jetty.SSIResourceHandler.paths=/" >> $JVB_CONFIG
echo "org.jitsi.videobridge.rest.jetty.tls.port=443" >> $JVB_CONFIG
echo "org.jitsi.videobridge.TCP_HARVESTER_PORT=443" >> $JVB_CONFIG
echo "org.jitsi.videobridge.rest.jetty.sslContextFactory.keyStorePath=/etc/jitsi/videobridge/$JVB_HOSTNAME.jks" >> $JVB_CONFIG

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,377 +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.
* @constructor
*/
function JitsiMeetExternalAPI(domain, room_name, width, height, parentNode,
configOverwrite, interfaceConfigOverwrite) {
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 = "//" + 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;
})();

Binary file not shown.

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

Binary file not shown.

View File

@@ -1,5 +1,7 @@
<html itemscope itemtype="http://schema.org/Product" prefix="og: http://ogp.me/ns#" xmlns="http://www.w3.org/1999/html">
<head>
<meta charset="utf-8" />
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<!--#include virtual="base.html" -->
<!--#include virtual="title.html" -->
<script>
@@ -229,16 +231,21 @@
</div>
<div id="contactlist" class="right-panel" style="display:none;">
<div class="title">
<i class="icon-contactList"><span data-i18n="contactlist"></span></i>
<i class="icon-contactList"></i>
<span data-i18n="contactlist"></span>
</div>
<ul id="contacts"></ul>
</div>
<div id="settingsmenu" class="right-panel" style="display:none;">
<div class="icon-settings" data-i18n="settings.title"></div>
<div class="title">
<i class="icon-settings"></i>
<span data-i18n="settings.title"></span>
</div>
<img id="avatar" src="images/avatar2.png"/>
<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.avatarUrl">
<select id="languages_selectbox"></select>
<div id = "startMutedOptions">
<label class = "startMutedLabel">
@@ -276,5 +283,72 @@
<a id="feedbackButton" data-container="body" data-toggle="popover" data-placement="right" data-i18n="[data-content]feedback"><i class="fa fa-heart"></i></a>
</div>
</div>
<div id="keyboard-shortcuts" class="keyboard-shortcuts" style="display:none;">
<div class="header"><h3 data-i18n="keyboardShortcuts.keyboardShortcuts"></h3></div>
<div class="content">
<ul class="item">
<li>
<span class="item-action">
<kbd class="regular-key">M</kbd>
</span>
<span class="item-description" data-i18n="keyboardShortcuts.mute"></span>
</li>
<li>
<span class="item-action">
<kbd class="regular-key">V</kbd>
</span>
<span class="item-description" data-i18n="keyboardShortcuts.videoMute"></span>
</li>
<li>
<span class="item-action">
<kbd class="regular-key">C</kbd>
</span>
<span class="item-description" data-i18n="keyboardShortcuts.toggleChat"></span>
</li>
<li>
<span class="item-action">
<kbd class="regular-key">R</kbd>
</span>
<span class="item-description" data-i18n="keyboardShortcuts.raiseHand"></span>
</li>
<li>
<span class="item-action">
<kbd class="regular-key">T</kbd>
</span>
<span class="item-description" data-i18n="keyboardShortcuts.pushToTalk"></span>
</li>
<li>
<span class="item-action">
<kbd class="regular-key">D</kbd>
</span>
<span class="item-description" data-i18n="keyboardShortcuts.toggleScreensharing"></span>
</li>
<li class="item-details">
<span class="item-action">
<kbd class="regular-key">F</kbd>
</span>
<span class="item-description" data-i18n="keyboardShortcuts.toggleFilmstrip"></span>
</li>
<li>
<span class="item-action">
<kbd class="regular-key">?</kbd>
</span>
<span class="item-description" data-i18n="keyboardShortcuts.toggleShortcuts"></span>
</li>
<li>
<span class="item-action">
<kbd class="regular-key">0</kbd>
</span>
<span class="item-description" data-i18n="keyboardShortcuts.focusLocal"></span>
</li>
<li>
<span class="item-action">
<kbd class="regular-key">1-9</kbd>
</span>
<span class="item-description" data-i18n="keyboardShortcuts.focusRemote"></span>
</li>
</ul>
</div>
</div>
</body>
</html>

View File

@@ -1,11 +1,15 @@
{
"en": "English",
"bg": "Bulgarian",
"de": "German",
"tr": "Turkish",
"it": "Italian",
"es": "Spanish",
"fr": "French",
"sl": "Slovenian",
"hy": "Armenian",
"it": "Italian",
"oc": "Occitan",
"sk": "Slovak",
"sv": "Swedish"
"sl": "Slovenian",
"sv": "Swedish",
"tr": "Turkish"
}

View File

@@ -10,6 +10,20 @@
"speaker": "Sprecher",
"defaultNickname": "Bsp: Heidi Blau",
"defaultLink": "Bsp.: __url__",
"calling": "Rufe __name__ an...",
"keyboardShortcuts": {
"keyboardShortcuts": "Tastaturkürzel:",
"raiseHand": "Heben Sie Ihre Hand.",
"pushToTalk": "Drücken um zu sprechen.",
"toggleScreensharing": "Zwischen Kamera und Bildschirmfreigabe wechseln.",
"toggleFilmstrip": "Videovorschau anzeigen oder verstecken.",
"toggleShortcuts": "Hilfe-Menu anzeigen oder verdecken.",
"focusLocal": "Lokales Video fokussieren.",
"focusRemote": "Andere Videos fokussieren.",
"toggleChat": "Chat öffnen oder schliessen.",
"mute": "Stummschaltung aktivieren oder deaktivieren.",
"videoMute": "Eigenes Video starten oder stoppen."
},
"welcomepage": {
"go": "Los",
"roomname": "Raumnamen eingeben",
@@ -66,7 +80,9 @@
"dialpad": "Tastenblock anzeigen",
"sharedVideoMutedPopup": "Das geteilte Video wurde stumm geschaltet damit mit <br/>den anderen Teilnehmern gesprochen werden kann.",
"micMutedPopup": "Ihr Mikrofon wurde stumm geschaltet damit das<br/>geteilte Video genossen werden kann.",
"unableToUnmutePopup": "Die Stummschaltung kann nicht aufgehoben werden während das geteilte Video abgespielt wird."
"unableToUnmutePopup": "Die Stummschaltung kann nicht aufgehoben werden während das geteilte Video abgespielt wird.",
"cameraDisabled": "Keine Kamera verfügbar",
"micDisabled": "Kein Mikrofon verfügbar"
},
"bottomtoolbar": {
"chat": "Chat öffnen / schließen",
@@ -88,7 +104,11 @@
"startVideoMuted": "Ohne Video beitreten",
"selectCamera": "Kamera auswählen",
"selectMic": "Mikrofon auswählen",
"followMe": "Follow-me aktivieren"
"selectAudioOutput": "Audio-Ausgabe auswählen",
"followMe": "Follow-me aktivieren",
"noDevice": "Kein",
"noPermission": "Keine Berechtigung um das Gerät zu verwenden",
"avatarUrl": "Avatar URL"
},
"videothumbnail": {
"editnickname": "Klicken, um den Anzeigenamen zu bearbeiten",
@@ -97,7 +117,8 @@
"mute": "Teilnehmer ist stumm geschaltet",
"kick": "Hinauswerfen",
"muted": "Stummgeschaltet",
"domute": "Stummschalten"
"domute": "Stummschalten",
"flip": "Spiegeln"
},
"connectionindicator": {
"bitrate": "Bitrate:",
@@ -177,7 +198,7 @@
"joinAgain": "Erneut beitreten",
"Share": "Teilen",
"Save": "Speichern",
"recording": "",
"recording": "Aufnahme",
"recordingToken": "Aufnahme-Token eingeben",
"Dial": "Wählen",
"sipMsg": "Geben Sie eine SIP Nummer ein",
@@ -205,13 +226,27 @@
"feedbackQuestion": "Wie war der Anruf?",
"thankYou": "Danke für die Verwendung von __appName__!",
"sorryFeedback": "Tut uns leid. Möchten Sie uns mehr mitteilen?",
"liveStreaming": "",
"liveStreaming": "Live-Streaming",
"streamKey": "Streamname/-schlüssel",
"startLiveStreaming": "Live-Streaming starten",
"stopStreamingWarning": "Sind Sie sicher dass Sie das Live-Streaming stoppen möchten?",
"stopRecordingWarning": "Sind Sie sicher dass Sie die Aufnahme stoppen möchten?",
"stopLiveStreaming": "Live-Streaming stoppen",
"stopRecording": "Aufnahme stoppen"
"stopRecording": "Aufnahme stoppen",
"doNotShowWarningAgain": "Diesen Hinweis nicht mehr anzeigen",
"permissionDenied": "Zugriff verweigert",
"screenSharingPermissionDeniedError": "Sie haben die Berechtigung für die Bildschirmfreigabe nicht erteilt.",
"micErrorPresent": "Fehler beim Verbinden zum Mikrofon.",
"cameraErrorPresent": "Fehler beim Verbinden zur Kamera.",
"cameraUnsupportedResolutionError": "Die Kamera unterstützt die erforderliche Auflösung nicht.",
"cameraUnknownError": "Die Kamera kann aus einem unbekannten Grund nicht verwendet werden.",
"cameraPermissionDeniedError": "Sie haben die Berechtigung für die Benutzung der Kamera nicht erteilt.",
"cameraNotFoundError": "Die angeforderte Kamera konnte nicht gefunden werden.",
"cameraConstraintFailedError": "Ihre Kamera erfüllt die notwendigen Anforderungen nicht.",
"micUnknownError": "Das Mikrofon kann aus einem unbekannten Grund nicht verwendet werden.",
"micPermissionDeniedError": "Sie haben die Berechtigung für die Benutzung des Mikrofons nicht erteilt.",
"micNotFoundError": "Das angeforderte Mikrofon konnte nicht gefunden werden.",
"micConstraintFailedError": "Ihr Mikrofon erfüllt die notwendigen Anforderungen nicht."
},
"\u0005dialog": {},
"email": {
@@ -262,7 +297,9 @@
"on": "Aufnahme",
"off": "Aufnahme gestoppt",
"failedToStart": "Die Aufnahme konnte nicht gestartet werden",
"buttonTooltip": "Aufnahme starten / stoppen"
"buttonTooltip": "Aufnahme starten / stoppen",
"error": "Die Aufzeichnung ist fehlgeschlagen. Bitte versuchen Sie es erneut.",
"unavailable": "Der Aufnahmedienst steht momentan nicht zur Verfügung. Bitte versuchen Sie es später noch einmal."
},
"liveStreaming": {
"pending": "Live-Stream wird gestartet...",
@@ -271,6 +308,8 @@
"unavailable": "Der Live-Streaming Dienst ist momentan nicht verfügbar. Bitte versuchen Sie es später noch einmal.",
"failedToStart": "Live-Streaming konnte nicht gestartet werden",
"buttonTooltip": "Live-Stream starten / stoppen",
"streamIdRequired": "Bitte Stream-ID eingeben um das Live-Streaming zu starten."
"streamIdRequired": "Bitte Stream-ID eingeben um das Live-Streaming zu starten.",
"error": "Das Live-Streaming ist fehlgeschlagen. Bitte versuchen Sie es erneut.",
"busy": "All Aufnahmegeräte sind momentan ausgelastet. Bitte versuchen Sie es später noch einmal."
}
}

View File

@@ -8,8 +8,33 @@
"participant": "Participant",
"me": "me",
"speaker": "Speaker",
"raisedHand": "Would like to speak",
"defaultNickname": "ex. Jane Pink",
"defaultLink": "e.g. __url__",
"calling": "Calling __name__ ...",
"userMedia": {
"react-nativeGrantPermissions": "Please grant permissions to use your camera and microphone by pressing <b><i>Allow</i></b> button",
"chromeGrantPermissions": "Please grant permissions to use your camera and microphone by pressing <b><i>Allow</i></b> button",
"androidGrantPermissions": "Please grant permissions to use your camera and microphone by pressing <b><i>Allow</i></b> button",
"firefoxGrantPermissions": "Please grant permissions to use your camera and microphone by pressing <b><i>Share Selected Device</i></b> button",
"operaGrantPermissions": "Please grant permissions to use your camera and microphone by pressing <b><i>Allow</i></b> button",
"iexplorerGrantPermissions": "Please grant permissions to use your camera and microphone by pressing <b><i>OK</i></b> button",
"safariGrantPermissions": "Please grant permissions to use your camera and microphone by pressing <b><i>OK</i></b> button",
"nwjsGrantPermissions": "Please grant permissions to use your camera and microphone"
},
"keyboardShortcuts": {
"keyboardShortcuts": "Keyboard shortcuts:",
"raiseHand": "Raise your hand.",
"pushToTalk": "Push to talk.",
"toggleScreensharing": "Switch between camera and screensharing.",
"toggleFilmstrip": "Show or hide the filmstrip.",
"toggleShortcuts": "Show or hide this help menu.",
"focusLocal": "Focus on the local video.",
"focusRemote": "Focus on one of the remote videos.",
"toggleChat": "Open or close the chat panel.",
"mute": "Mute or unmute the microphone.",
"videoMute": "Stop or start the local video."
},
"welcomepage":{
"go": "GO",
"roomname": "Enter room name",
@@ -94,7 +119,8 @@
"selectAudioOutput": "Select audio output",
"followMe": "Enable follow me",
"noDevice": "None",
"noPermission": "Permission to use device is not granted"
"noPermission": "Permission to use device is not granted",
"avatarUrl": "Avatar URL"
},
"videothumbnail":
{
@@ -139,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!",
@@ -222,7 +249,21 @@
"stopStreamingWarning": "Are you sure you would like to stop the live streaming?",
"stopRecordingWarning": "Are you sure you would like to stop the recording?",
"stopLiveStreaming": "Stop live streaming",
"stopRecording": "Stop recording"
"stopRecording": "Stop recording",
"doNotShowWarningAgain": "Don't show this warning again",
"permissionDenied": "Permission Denied",
"screenSharingPermissionDeniedError": "You have not granted permission to share your screen.",
"micErrorPresent": "There was an error connecting to your microphone.",
"cameraErrorPresent": "There was an error connecting to your camera.",
"cameraUnsupportedResolutionError": "Your camera does not support required video resolution.",
"cameraUnknownError": "Cannot use camera for a unknown reason.",
"cameraPermissionDeniedError": "You have not granted permission to use your camera. You can still join the conference but others won't see you. Use the camera button in the address bar to fix this.",
"cameraNotFoundError": "Requested camera was not found.",
"cameraConstraintFailedError": "Yor camera does not satisfy some of required constraints.",
"micUnknownError": "Cannot use microphone for a unknown reason.",
"micPermissionDeniedError": "You have not granted permission to use your microphone. You can still join the conference but others won't hear you. Use the camera button in the address bar to fix this.",
"micNotFoundError": "Requested microphone was not found.",
"micConstraintFailedError": "Yor microphone does not satisfy some of required constraints."
},
"email":
{

View File

@@ -1,10 +1,12 @@
/* 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 postisInit from 'postis';
/**
* List of the available commands.
* @type {{
@@ -16,17 +18,43 @@
* toggleContactList: toggleContactList
* }}
*/
var commands = {};
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;
/**
* Postis instance. Used to communicate with the external application.
*/
let postis;
/**
* Current status (enabled/disabled) of API.
*/
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]);
});
}
@@ -34,94 +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.");
}
}
/**
* Sends message to the external application.
* @param object
* @param message {object}
* @param method {string}
* @param params {object} the object that will be sent as JSON string
*/
function sendMessage(object) {
window.parent.postMessage(JSON.stringify(object), "*");
}
/**
* 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) {}
if(!message.type)
return;
switch (message.type) {
case "command":
processCommand(message);
break;
case "event":
processEvent(message);
break;
default:
console.error("Unknown type of the message");
return;
}
function sendMessage(message) {
if(enabled)
postis.send(message);
}
/**
@@ -129,8 +97,7 @@ function processMessage(event) {
* @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");
}
/**
@@ -149,13 +116,25 @@ function isEventEnabled (name) {
* @param object data associated with the event
*/
function triggerEvent (name, object) {
if (isEnabled() && isEventEnabled(name)) {
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);
}
}
@@ -165,18 +144,28 @@ export default {
* receive information from external applications that embed Jitsi Meet.
* It also sends a message to the external application that APIConnector
* is initialized.
* @param options {object}
* @param forceEnable {boolean} if true the module will be enabled.
* @param enabledEvents {array} array of events that should be enabled.
*/
init: function () {
if (!isEnabled()) {
init (options = {}) {
if(!isEnabled() && !options.forceEnable)
return;
}
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();
if (window.addEventListener) {
window.addEventListener('message', processMessage, false);
} else {
window.attachEvent('onmessage', processMessage);
}
sendMessage({type: "system", loaded: true});
},
/**
@@ -184,7 +173,7 @@ export default {
* @param {string} body message body
*/
notifySendingChatMessage (body) {
triggerEvent("outgoingMessage", {"message": body});
triggerEvent("outgoing-message", {"message": body});
},
/**
@@ -201,7 +190,7 @@ export default {
}
triggerEvent(
"incomingMessage",
"incoming-message",
{"from": id, "nick": nick, "message": body, "stamp": ts}
);
},
@@ -212,7 +201,7 @@ export default {
* @param {string} id user id
*/
notifyUserJoined (id) {
triggerEvent("participantJoined", {id});
triggerEvent("participant-joined", {id});
},
/**
@@ -221,7 +210,7 @@ export default {
* @param {string} id user id
*/
notifyUserLeft (id) {
triggerEvent("participantLeft", {id});
triggerEvent("participant-left", {id});
},
/**
@@ -231,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 (!isEnabled()) {
return;
}
if (window.removeEventListener) {
window.removeEventListener("message", processMessage, false);
} else {
window.detachEvent('onmessage', processMessage);
}
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

@@ -154,9 +154,10 @@ class FollowMe {
this._nextOnStage(smallVideo, isPinned);
this._sharedDocumentToggled
.bind(this, this._UI.getSharedDocumentManager().isVisible());
// check whether shared document is enabled/initialized
if(this._UI.getSharedDocumentManager())
this._sharedDocumentToggled
.bind(this, this._UI.getSharedDocumentManager().isVisible());
}
/**
@@ -352,10 +353,13 @@ class FollowMe {
_onNextOnStage(id) {
var clickId = null;
var pin;
// if there is an id which is not pinned we schedule it for pin only the
// first time
if(typeof id !== 'undefined' && !VideoLayout.isPinned(id)) {
clickId = id;
pin = true;
}
// if there is no id, but we have a pinned one, let's unpin
else if (typeof id == 'undefined' && VideoLayout.getPinnedId()) {
clickId = VideoLayout.getPinnedId();
pin = false;

View File

@@ -0,0 +1,121 @@
/* global getConfigParamsFromUrl, config */
/**
* Parses and handles JWT tokens. Sets config.token.
*/
import * as jws from "jws";
/**
* Get the JWT token from the URL.
*/
let params = getConfigParamsFromUrl("search", true);
let jwt = params.jwt;
/**
* Implements a user of conference.
*/
class User {
/**
* @param name {string} the name of the user.
* @param email {string} the email of the user.
* @param avatarUrl {string} the URL for the avatar of the user.
*/
constructor(name, email, avatarUrl) {
this._name = name;
this._email = email;
this._avatarUrl = avatarUrl;
}
/**
* GETERS START.
*/
/**
* Returns the name property
*/
getName() {
return this._name;
}
/**
* Returns the email property
*/
getEmail() {
return this._email;
}
/**
* Returns the URL of the avatar
*/
getAvatarUrl() {
return this._avatarUrl;
}
/**
* GETERS END.
*/
}
/**
* Represent the data parsed from the JWT token
*/
class TokenData{
/**
* @param {string} the JWT token
*/
constructor(jwt) {
if(!jwt)
return;
this.jwt = jwt;
//External API settings
this.externalAPISettings = {
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
// iss field is not anonymous. If you want to pass data with JWT token
// but you don't want to pass the JWT token for verification the iss
// field should be set to "anonymous"
if(!config.token && this.payload && this.payload.iss !== "anonymous")
config.token = jwt;
}
/**
* Decodes the JWT token and sets the decoded data to properties.
*/
_decode() {
this.decodedJWT = jws.decode(jwt);
if(!this.decodedJWT || !this.decodedJWT.payload)
return;
this.payload = this.decodedJWT.payload;
if(!this.payload.context)
return;
let callerData = this.payload.context.user;
let calleeData = this.payload.context.callee;
if(callerData)
this.caller = new User(callerData.name, callerData.email,
callerData.avatarUrl);
if(calleeData)
this.callee = new User(calleeData.name, calleeData.email,
calleeData.avatarUrl);
}
}
/**
* Stores the TokenData instance.
*/
let data = null;
/**
* Returns the data variable. Creates new TokenData instance if <tt>data</tt>
* variable is null.
*/
export default function getTokenData() {
if(!data)
data = new TokenData(jwt);
return data;
}

View File

@@ -1,11 +1,6 @@
/* global $, APP, config, interfaceConfig */
import UIEvents from "../../service/UI/UIEvents";
/*
* Created by Yana Stamcheva on 2/10/15.
*/
var messageHandler = require("./util/MessageHandler");
/**
* Constructs the html for the overall feedback window.
*

View File

@@ -1,4 +1,4 @@
/* global APP, $, config, interfaceConfig, toastr */
/* global APP, JitsiMeetJS, $, config, interfaceConfig, toastr */
/* jshint -W101 */
var UI = {};
@@ -15,12 +15,14 @@ import CQEvents from '../../service/connectionquality/CQEvents';
import EtherpadManager from './etherpad/Etherpad';
import SharedVideoManager from './shared_video/SharedVideo';
import Recording from "./recording/Recording";
import GumPermissionsOverlay from './gum_overlay/UserMediaPermissionsGuidanceOverlay';
import VideoLayout from "./videolayout/VideoLayout";
import FilmStrip from "./videolayout/FilmStrip";
import SettingsMenu from "./side_pannels/settings/SettingsMenu";
import Settings from "./../settings/Settings";
import { reload } from '../util/helpers';
import RingOverlay from "./ring_overlay/RingOverlay";
var EventEmitter = require("events");
UI.messageHandler = require("./util/MessageHandler");
@@ -38,6 +40,34 @@ let sharedVideoManager;
let followMeHandler;
let deviceErrorDialog;
const TrackErrors = JitsiMeetJS.errors.track;
const JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP = {
microphone: {},
camera: {}
};
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[TrackErrors.UNSUPPORTED_RESOLUTION]
= "dialog.cameraUnsupportedResolutionError";
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[TrackErrors.GENERAL]
= "dialog.cameraUnknownError";
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[TrackErrors.PERMISSION_DENIED]
= "dialog.cameraPermissionDeniedError";
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[TrackErrors.NOT_FOUND]
= "dialog.cameraNotFoundError";
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[TrackErrors.CONSTRAINT_FAILED]
= "dialog.cameraConstraintFailedError";
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[TrackErrors.GENERAL]
= "dialog.micUnknownError";
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[TrackErrors.PERMISSION_DENIED]
= "dialog.micPermissionDeniedError";
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[TrackErrors.NOT_FOUND]
= "dialog.micNotFoundError";
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[TrackErrors.CONSTRAINT_FAILED]
= "dialog.micConstraintFailedError";
/**
* Prompt user for nickname.
*/
@@ -228,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;
@@ -251,7 +299,7 @@ UI.initConference = function () {
}
// Make sure we configure our avatar id, before creating avatar for us
UI.setUserAvatar(id, Settings.getEmail());
UI.setUserEmail(id, Settings.getEmail());
Toolbar.checkAutoEnableDesktopSharing();
@@ -362,6 +410,7 @@ UI.start = function () {
registerListeners();
ToolbarToggler.init();
BottomToolbar.init();
FilmStrip.init(eventEmitter);
@@ -404,7 +453,7 @@ UI.start = function () {
$("#downloadlog").css("display", "none");
BottomToolbar.hide();
FilmStrip.setupFilmStripOnly();
messageHandler.disableNotifications();
messageHandler.enableNotifications(false);
$('body').popover("disable");
JitsiPopover.enabled = false;
}
@@ -444,6 +493,10 @@ UI.start = function () {
SettingsMenu.init(eventEmitter);
}
if(APP.tokenData.callee) {
UI.showRingOverLay();
}
// Return true to indicate that the UI has been fully started and
// conference ready.
return true;
@@ -525,6 +578,7 @@ UI.getSharedDocumentManager = function () {
* @param {string} displayName user nickname
*/
UI.addUser = function (id, displayName) {
UI.hideRingOverLay();
ContactList.addContact(id);
messageHandler.notify(
@@ -539,7 +593,7 @@ UI.addUser = function (id, displayName) {
VideoLayout.addParticipantContainer(id);
// Configure avatar
UI.setUserAvatar(id);
UI.setUserEmail(id);
// set initial display name
if(displayName)
@@ -774,21 +828,41 @@ UI.dockToolbar = function (isDock) {
};
/**
* Update user avatar.
* Updates the avatar for participant.
* @param {string} id user id
* @param {stirng} email user email
* @param {stirng} avatarUrl the URL for the avatar
*/
UI.setUserAvatar = function (id, email) {
// update avatar
Avatar.setUserAvatar(id, email);
var avatarUrl = Avatar.getAvatarUrl(id);
function changeAvatar(id, avatarUrl) {
VideoLayout.changeUserAvatar(id, avatarUrl);
ContactList.changeUserAvatar(id, avatarUrl);
if (APP.conference.isLocalId(id)) {
SettingsMenu.changeAvatar(avatarUrl);
}
}
/**
* Update user email.
* @param {string} id user id
* @param {stirng} email user email
*/
UI.setUserEmail = function (id, email) {
// update avatar
Avatar.setUserEmail(id, email);
changeAvatar(id, Avatar.getAvatarUrl(id));
};
/**
* Update user avatar URL.
* @param {string} id user id
* @param {stirng} url user avatar url
*/
UI.setUserAvatarUrl = function (id, url) {
// update avatar
Avatar.setUserAvatarUrl(id, url);
changeAvatar(id, Avatar.getAvatarUrl(id));
};
/**
@@ -1078,6 +1152,28 @@ UI.onAvailableDevicesChanged = function (devices) {
SettingsMenu.changeDevicesList(devices);
};
/**
* Sets microphone's <select> element to select microphone ID from settings.
*/
UI.setSelectedMicFromSettings = function () {
SettingsMenu.setSelectedMicFromSettings();
};
/**
* Sets camera's <select> element to select camera ID from settings.
*/
UI.setSelectedCameraFromSettings = function () {
SettingsMenu.setSelectedCameraFromSettings();
};
/**
* Sets audio outputs's <select> element to select audio output ID from
* settings.
*/
UI.setSelectedAudioOutputFromSettings = function () {
SettingsMenu.setSelectedAudioOutputFromSettings();
};
/**
* Returns the id of the current video shown on large.
* Currently used by tests (torture).
@@ -1106,6 +1202,141 @@ UI.showExtensionRequiredDialog = function (url) {
"dialog.firefoxExtensionPrompt", {url: url}));
};
/**
* Shows dialog with combined information about camera and microphone errors.
* @param {JitsiTrackError} micError
* @param {JitsiTrackError} cameraError
*/
UI.showDeviceErrorDialog = function (micError, cameraError) {
let localStoragePropName = "doNotShowErrorAgain";
let isMicJitsiTrackErrorAndHasName = micError && micError.name &&
micError instanceof JitsiMeetJS.JitsiTrackError;
let isCameraJitsiTrackErrorAndHasName = cameraError && cameraError.name &&
cameraError instanceof JitsiMeetJS.JitsiTrackError;
let showDoNotShowWarning = false;
if (micError && cameraError && isMicJitsiTrackErrorAndHasName &&
isCameraJitsiTrackErrorAndHasName) {
showDoNotShowWarning = true;
} else if (micError && isMicJitsiTrackErrorAndHasName && !cameraError) {
showDoNotShowWarning = true;
} else if (cameraError && isCameraJitsiTrackErrorAndHasName && !micError) {
showDoNotShowWarning = true;
}
if (micError) {
localStoragePropName += "-mic-" + micError.name;
}
if (cameraError) {
localStoragePropName += "-camera-" + cameraError.name;
}
if (showDoNotShowWarning) {
if (window.localStorage[localStoragePropName] === "true") {
return;
}
}
let title = getTitleKey();
let titleMsg = `<span data-i18n="${title}"></span>`;
let cameraJitsiTrackErrorMsg = cameraError
? JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[cameraError.name]
: undefined;
let micJitsiTrackErrorMsg = micError
? JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[micError.name]
: undefined;
let cameraErrorMsg = cameraError
? cameraJitsiTrackErrorMsg ||
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[TrackErrors.GENERAL]
: "";
let micErrorMsg = micError
? micJitsiTrackErrorMsg ||
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[TrackErrors.GENERAL]
: "";
let additionalCameraErrorMsg = !cameraJitsiTrackErrorMsg && cameraError &&
cameraError.message
? `<div>${cameraError.message}</div>`
: ``;
let additionalMicErrorMsg = !micJitsiTrackErrorMsg && micError &&
micError.message
? `<div>${micError.message}</div>`
: ``;
let doNotShowWarningAgainSection = showDoNotShowWarning
? `<label>
<input type='checkbox' id='doNotShowWarningAgain'>
<span data-i18n='dialog.doNotShowWarningAgain'></span>
</label>`
: ``;
let message = '';
if (micError) {
message = `
${message}
<h3 data-i18n='dialog.micErrorPresent'></h3>
<h4 data-i18n='${micErrorMsg}'></h4>
${additionalMicErrorMsg}`;
}
if (cameraError) {
message = `
${message}
<h3 data-i18n='dialog.cameraErrorPresent'></h3>
<h4 data-i18n='${cameraErrorMsg}'></h4>
${additionalCameraErrorMsg}`;
}
message = `${message}${doNotShowWarningAgainSection}`;
// 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,
{Ok: true},
function () {
let form = $.prompt.getPrompt();
if (form) {
let input = form.find("#doNotShowWarningAgain");
if (input.length) {
window.localStorage[localStoragePropName] =
input.prop("checked");
}
}
},
null,
function () {
// Reset dialog reference to null to avoid memory leaks when
// user closed the dialog manually.
deviceErrorDialog = null;
}
);
APP.translation.translateElement($(".jqibox"));
function getTitleKey() {
let title = "dialog.error";
if (micError && micError.name === TrackErrors.PERMISSION_DENIED) {
if (cameraError && cameraError.name === TrackErrors.PERMISSION_DENIED) {
title = "dialog.permissionDenied";
} else if (!cameraError) {
title = "dialog.permissionDenied";
}
} else if (cameraError &&
cameraError.name === TrackErrors.PERMISSION_DENIED) {
title = "dialog.permissionDenied";
}
return title;
}
};
UI.updateDevicesAvailability = function (id, devices) {
VideoLayout.setDeviceAvailabilityIcons(id, devices);
};
@@ -1170,4 +1401,53 @@ UI.enableMicrophoneButton = function () {
Toolbar.markAudioIconAsDisabled(false);
};
let bottomToolbarEnabled = null;
UI.showRingOverLay = function () {
RingOverlay.show(APP.tokenData.callee);
ToolbarToggler.setAlwaysVisibleToolbar(true);
FilmStrip.toggleFilmStrip(false);
};
UI.hideRingOverLay = function () {
if (!RingOverlay.hide())
return;
ToolbarToggler.resetAlwaysVisibleToolbar();
FilmStrip.toggleFilmStrip(true);
};
/**
* Shows browser-specific overlay with guidance how to proceed with gUM prompt.
* @param {string} browser - name of browser for which to show the guidance
* overlay.
*/
UI.showUserMediaPermissionsGuidanceOverlay = function (browser) {
GumPermissionsOverlay.show(browser);
};
/**
* Hides browser-specific overlay with guidance how to proceed with gUM prompt.
*/
UI.hideUserMediaPermissionsGuidanceOverlay = function () {
GumPermissionsOverlay.hide();
};
/**
* 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

@@ -1,7 +1,5 @@
/* global $, APP, config*/
var messageHandler = require('../util/MessageHandler');
/**
* Build html for "password required" dialog.
* @returns {string} html string
@@ -123,7 +121,7 @@ function LoginDialog(successCallback, cancelCallback) {
}
};
var connDialog = messageHandler.openDialogWithStates(
var connDialog = APP.UI.messageHandler.openDialogWithStates(
states, { persistent: true, closeText: '' }, null
);
@@ -182,14 +180,14 @@ export default {
* @returns auth dialog
*/
showExternalAuthDialog: function (url, callback) {
var dialog = messageHandler.openCenteredPopup(
var dialog = APP.UI.messageHandler.openCenteredPopup(
url, 910, 660,
// On closed
callback
);
if (!dialog) {
messageHandler.openMessageDialog(null, "dialog.popupError");
APP.UI.messageHandler.openMessageDialog(null, "dialog.popupError");
}
return dialog;

View File

@@ -1,5 +1,4 @@
/* global APP, JitsiMeetJS */
import messageHandler from '../util/MessageHandler';
import UIUtil from '../util/UIUtil';
//FIXME:
import AnalyticsAdapter from '../../statistics/AnalyticsAdapter';
@@ -19,7 +18,7 @@ function askForNewPassword () {
`;
return new Promise(function (resolve, reject) {
messageHandler.openTwoButtonDialog(
APP.UI.messageHandler.openTwoButtonDialog(
null, null, null,
msg, false, "dialog.Save",
function (e, v, m, f) {
@@ -27,7 +26,7 @@ function askForNewPassword () {
resolve(UIUtil.escapeHtml(f.lockKey));
}
else {
reject(messageHandler.CANCEL);
reject(APP.UI.messageHandler.CANCEL);
}
},
null, null, 'input:first'
@@ -51,7 +50,7 @@ function askForPassword () {
placeholder="${passMsg}" autofocus>
`;
return new Promise(function (resolve, reject) {
messageHandler.openTwoButtonDialog(
APP.UI.messageHandler.openTwoButtonDialog(
null, null, null, msg,
true, "dialog.Ok",
function (e, v, m, f) {}, null,
@@ -59,7 +58,7 @@ function askForPassword () {
if (v && f.lockKey) {
resolve(UIUtil.escapeHtml(f.lockKey));
} else {
reject(messageHandler.CANCEL);
reject(APP.UI.messageHandler.CANCEL);
}
},
':input:first'
@@ -73,14 +72,14 @@ function askForPassword () {
*/
function askToUnlock () {
return new Promise(function (resolve, reject) {
messageHandler.openTwoButtonDialog(
APP.UI.messageHandler.openTwoButtonDialog(
null, null, "dialog.passwordCheck",
null, false, "dialog.Remove",
function (e, v) {
if (v) {
resolve();
} else {
reject(messageHandler.CANCEL);
reject(APP.UI.messageHandler.CANCEL);
}
}
);
@@ -93,7 +92,8 @@ function askToUnlock () {
*/
function notifyPasswordNotSupported () {
console.warn('room passwords not supported');
messageHandler.showError("dialog.warning", "dialog.passwordNotSupported");
APP.UI.messageHandler.showError(
"dialog.warning", "dialog.passwordNotSupported");
}
/**
@@ -102,7 +102,8 @@ function notifyPasswordNotSupported () {
*/
function notifyPasswordFailed(err) {
console.warn('setting password failed', err);
messageHandler.showError("dialog.lockTitle", "dialog.lockMessage");
APP.UI.messageHandler.showError(
"dialog.lockTitle", "dialog.lockMessage");
}
const ConferenceErrors = JitsiMeetJS.errors.conference;
@@ -153,7 +154,7 @@ export default function createRoomLocker (room) {
AnalyticsAdapter.sendEvent('toolbar.lock.disabled');
}).catch(
reason => {
if (reason !== messageHandler.CANCEL)
if (reason !== APP.UI.messageHandler.CANCEL)
console.error(reason);
}
);
@@ -171,7 +172,7 @@ export default function createRoomLocker (room) {
AnalyticsAdapter.sendEvent('toolbar.lock.enabled');
}).catch(
reason => {
if (reason !== messageHandler.CANCEL)
if (reason !== APP.UI.messageHandler.CANCEL)
console.error(reason);
}
);
@@ -185,7 +186,7 @@ export default function createRoomLocker (room) {
newPass => { password = newPass; }
).catch(
reason => {
if (reason !== messageHandler.CANCEL)
if (reason !== APP.UI.messageHandler.CANCEL)
console.error(reason);
}
);
@@ -196,9 +197,11 @@ export default function createRoomLocker (room) {
*/
notifyModeratorRequired () {
if (password) {
messageHandler.openMessageDialog(null, "dialog.passwordError");
APP.UI.messageHandler
.openMessageDialog(null, "dialog.passwordError");
} else {
messageHandler.openMessageDialog(null, "dialog.passwordError2");
APP.UI.messageHandler
.openMessageDialog(null, "dialog.passwordError2");
}
}
};

View File

@@ -3,6 +3,19 @@
let users = {};
export default {
/**
* Sets prop in users object.
* @param id {string} user id
* @param prop {string} name of the prop
* @param val {string} value to be set
*/
_setUserProp: function (id, prop, val) {
if(!val || (users[id] && users[id][prop] === val))
return;
if(!users[id])
users[id] = {};
users[id][prop] = val;
},
/**
* Sets the user's avatar in the settings menu(if local user), contact list
@@ -10,13 +23,18 @@ export default {
* @param id id of the user
* @param email email or nickname to be used as a hash
*/
setUserAvatar: function (id, email) {
if (email) {
if (users[id] === email) {
return;
}
users[id] = email;
}
setUserEmail: function (id, email) {
this._setUserProp(id, "email", email);
},
/**
* Sets the user's avatar in the settings menu(if local user), contact list
* and thumbnail
* @param id id of the user
* @param url the url for the avatar
*/
setUserAvatarUrl: function (id, url) {
this._setUserProp(id, "url", url);
},
/**
@@ -34,7 +52,15 @@ export default {
return null;
}
let avatarId = users[userId];
let avatarId = null;
const user = users[userId];
if(user) {
if(user.url)
return users[userId].url;
avatarId = users[userId].email;
}
// If the ID looks like an email, we'll use gravatar.
// Otherwise, it's a random avatar, and we'll use the configured

View File

@@ -0,0 +1,46 @@
/* global $, APP, JitsiMeetJS */
let $overlay;
/**
* Internal function that constructs overlay with guidance how to proceed with
* gUM prompt.
* @param {string} browser - name of browser for which to construct the
* guidance overlay.
*/
function buildOverlayHtml(browser) {
$overlay = $(`
<div class='overlay_container'>
<div class='overlay overlay_transparent' />
<div class='overlay_content'>
<span class="overlay_icon icon-microphone"></span>
<span class="overlay_icon icon-camera"></span>
<span data-i18n='[html]userMedia.${browser}GrantPermissions'
class='overlay_text overlay_text_small'></span>
</div>
</div>`);
APP.translation.translateElement($overlay);
}
export default {
/**
* Shows browser-specific overlay with guidance how to proceed with
* gUM prompt.
* @param {string} browser - name of browser for which to show the
* guidance overlay.
*/
show(browser) {
!$overlay && buildOverlayHtml(browser);
!$overlay.parents('body').length && $overlay.appendTo('body');
},
/**
* Hides browser-specific overlay with guidance how to proceed with
* gUM prompt.
*/
hide() {
$overlay && $overlay.detach();
}
};

View File

@@ -236,6 +236,8 @@ var Recording = {
Feedback.enableFeedback(false);
Toolbar.enable(false);
BottomToolbar.enable(false);
APP.UI.messageHandler.enableNotifications(false);
APP.UI.messageHandler.enablePopups(false);
}
},

View File

@@ -0,0 +1,82 @@
/* global $ */
/**
* Shows ring overlay
*/
class RingOverlay {
/**
* @param callee instance of User class from TokenData.js
*/
constructor(callee) {
this.callee = callee;
this._buildHtml();
this.audio = $("#ring_overlay_ringing");
this.audio[0].play();
this._setAudioTimeout();
}
/**
* Builds and appends the ring overlay to the html document
*/
_buildHtml() {
$("body").append("<div class='overlay_container' >" +
"<div class='overlay' /><div class='overlay_content'>" +
"<img class='overlay_avatar' src='" +
this.callee.getAvatarUrl() + "' />" +
"<span data-i18n='calling' data-i18n-options='" +
JSON.stringify({name: this.callee.getName()}) +
"' class='overlay_text'>Calling " +
this.callee.getName() + "...</span></div>" +
"<audio id='ring_overlay_ringing' src='/sounds/ring.ogg' /></div>");
}
/**
* Sets the interval that is going to play the ringing sound.
*/
_setAudioTimeout() {
this.interval = setInterval( () => {
this.audio[0].play();
}, 5000);
}
/**
* Destroys and clears all the objects (html elements and audio interval)
* related to the ring overlay.
*/
destroy() {
if(this.interval)
clearInterval(this.interval);
$(".overlay_container").remove();
}
}
/**
* Store the current ring overlay instance.
* Note: We want to have only 1 instance at a time.
*/
let overlay = null;
export default {
/**
* Shows the ring overlay for the passed callee.
* @param callee {class User} the callee. Instance of User class from
* TokenData.js
*/
show(callee) {
if(overlay) {
this.hide();
}
overlay = new RingOverlay(callee);
},
/**
* Hides the ring overlay. Destroys all the elements related to the ring
* overlay.
*/
hide() {
if(!overlay)
return false;
overlay.destroy();
overlay = null;
return true;
}
};

View File

@@ -1,6 +1,5 @@
/* global $, APP, YT, onPlayerReady, onPlayerStateChange, onPlayerError */
import messageHandler from '../util/MessageHandler';
import UIUtil from '../util/UIUtil';
import UIEvents from '../../../service/UI/UIEvents';
@@ -71,7 +70,7 @@ export default class SharedVideoManager {
this.emitter.emit(
UIEvents.UPDATE_SHARED_VIDEO, this.url, 'stop'));
} else {
messageHandler.openMessageDialog(
APP.UI.messageHandler.openMessageDialog(
"dialog.shareVideoTitle",
"dialog.alreadySharedVideoMsg"
);
@@ -701,7 +700,7 @@ function getYoutubeLink(url) {
*/
function showStopVideoPropmpt() {
return new Promise(function (resolve, reject) {
messageHandler.openTwoButtonDialog(
APP.UI.messageHandler.openTwoButtonDialog(
"dialog.removeSharedVideoTitle",
null,
"dialog.removeSharedVideoMsg",
@@ -736,7 +735,7 @@ function requestVideoLink() {
const defaultUrl = i18n.translateString("defaultLink", i18nOptions);
return new Promise(function (resolve, reject) {
let dialog = messageHandler.openDialogWithStates({
let dialog = APP.UI.messageHandler.openDialogWithStates({
state0: {
html: `
<h2>${title}</h2>

View File

@@ -81,6 +81,12 @@ export default {
function updateEmail () {
emitter.emit(UIEvents.EMAIL_CHANGED, $('#setEmail').val());
}
// AVATAR URL CHANGED
function updateAvatarUrl () {
emitter.emit(UIEvents.AVATAR_URL_CHANGED, $('#setAvatarUrl').val());
}
$('#setEmail')
.val(Settings.getEmail())
.keyup(function (event) {
@@ -89,6 +95,14 @@ export default {
}
}).focusout(updateEmail);
$('#setAvatarUrl')
.val(Settings.getAvatarUrl())
.keyup(function (event) {
if (event.keyCode === 13) { // enter
updateAvatarUrl();
}
}).focusout(updateAvatarUrl);
// START MUTED
$("#startMutedOptions").change(function () {
@@ -203,6 +217,28 @@ export default {
$('#avatar').attr('src', avatarUrl);
},
/**
* Sets microphone's <select> element to select microphone ID from settings.
*/
setSelectedMicFromSettings () {
$('#selectMic').val(Settings.getMicDeviceId());
},
/**
* Sets camera's <select> element to select camera ID from settings.
*/
setSelectedCameraFromSettings () {
$('#selectCamera').val(Settings.getCameraDeviceId());
},
/**
* Sets audio outputs's <select> element to select audio output ID from
* settings.
*/
setSelectedAudioOutputFromSettings () {
$('#selectAudioOutput').val(Settings.getAudioOutputDeviceId());
},
/**
* Change available cameras/microphones or hide selects completely if
* no devices available.

View File

@@ -1,6 +1,5 @@
/* global APP, $, config, interfaceConfig */
/* jshint -W101 */
import messageHandler from '../util/MessageHandler';
import UIUtil from '../util/UIUtil';
import AnalyticsAdapter from '../../statistics/AnalyticsAdapter';
import UIEvents from '../../../service/UI/UIEvents';
@@ -21,7 +20,7 @@ function openLinkDialog () {
} else {
inviteAttributes = "value=\"" + encodeURI(roomUrl) + "\"";
}
messageHandler.openTwoButtonDialog(
APP.UI.messageHandler.openTwoButtonDialog(
"dialog.shareLink", null, null,
`<input id="inviteLinkRef" type="text" ${inviteAttributes} onclick="this.select();" readonly>`,
false, "dialog.Invite",
@@ -129,7 +128,7 @@ const buttonHandlers = {
"toolbar_button_logout": function () {
AnalyticsAdapter.sendEvent('toolbar.authenticate.logout.clicked');
// Ask for confirmation
messageHandler.openTwoButtonDialog(
APP.UI.messageHandler.openTwoButtonDialog(
"dialog.logoutTitle",
null,
"dialog.logoutQuestion",
@@ -167,7 +166,7 @@ function showSipNumberInput () {
: '';
let sipMsg = APP.translation.generateTranslationHTML("dialog.sipMsg");
messageHandler.openTwoButtonDialog(
APP.UI.messageHandler.openTwoButtonDialog(
null, null, null,
`<h2>${sipMsg}</h2>
<input name="sipNumber" type="text" value="${defaultNumber}" autofocus>`,

View File

@@ -7,6 +7,10 @@ import FilmStrip from '../videolayout/FilmStrip.js';
let toolbarTimeoutObject;
let toolbarTimeout = interfaceConfig.INITIAL_TOOLBAR_TIMEOUT;
/**
* If true the toolbar will be always displayed
*/
let alwaysVisibleToolbar = false;
function showDesktopSharingButton() {
if (APP.conference.isDesktopSharingEnabled &&
@@ -21,7 +25,7 @@ function showDesktopSharingButton() {
* Hides the toolbar.
*/
function hideToolbar() {
if (config.alwaysVisibleToolbar) {
if (alwaysVisibleToolbar) {
return;
}
@@ -40,6 +44,25 @@ function hideToolbar() {
}
const ToolbarToggler = {
/**
* Initializes the ToolbarToggler
*/
init() {
alwaysVisibleToolbar = (config.alwaysVisibleToolbar === true);
},
/**
* Sets the value of alwaysVisibleToolbar variable.
* @param value {boolean} the new value of alwaysVisibleToolbar variable
*/
setAlwaysVisibleToolbar(value) {
alwaysVisibleToolbar = value;
},
/**
* Resets the value of alwaysVisibleToolbar variable to the default one.
*/
resetAlwaysVisibleToolbar() {
alwaysVisibleToolbar = (config.alwaysVisibleToolbar === true);
},
/**
* Shows the main toolbar.
*/

View File

@@ -7,11 +7,18 @@ import UIUtil from './UIUtil';
* Flag for enable/disable of the notifications.
* @type {boolean}
*/
var notificationsEnabled = true;
let notificationsEnabled = true;
/**
* Flag for enabling/disabling popups.
* @type {boolean}
*/
let popupEnabled = true;
var messageHandler = {
OK: "dialog.OK",
CANCEL: "dialog.Cancel",
var messageHandler = (function(my) {
my.OK = "dialog.OK",
my.CANCEL = "dialog.Cancel",
/**
* Shows a message to the user.
*
@@ -24,7 +31,10 @@ var messageHandler = (function(my) {
* @param message the message to show. If a falsy value is provided,
* messageKey will be used to get a message via the translation API.
*/
my.openMessageDialog = function(titleKey, messageKey, title, message) {
openMessageDialog: function(titleKey, messageKey, title, message) {
if (!popupEnabled)
return;
if (!title) {
title = APP.translation.generateTranslationHTML(titleKey);
}
@@ -35,8 +45,7 @@ var messageHandler = (function(my) {
$.prompt(message,
{title: title, persistent: false}
);
};
},
/**
* Shows a message to the user with two buttons: first is given as a
* parameter and the second is Cancel.
@@ -55,9 +64,13 @@ var messageHandler = (function(my) {
* @param defaultButton index of default button which will be activated when
* the user press 'enter'. Indexed from 0.
*/
my.openTwoButtonDialog = function(titleKey, titleString, msgKey, msgString,
openTwoButtonDialog: function(titleKey, titleString, msgKey, msgString,
persistent, leftButtonKey, submitFunction, loadedFunction,
closeFunction, focus, defaultButton) {
if (!popupEnabled)
return;
var buttons = [];
var leftButton = APP.translation.generateTranslationHTML(leftButtonKey);
@@ -84,7 +97,7 @@ var messageHandler = (function(my) {
submit: submitFunction,
close: closeFunction
});
};
},
/**
* Shows a message to the user with two buttons: first is given as a
@@ -100,38 +113,48 @@ var messageHandler = (function(my) {
* @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
*/
my.openDialog = function (titleString, msgString, persistent, buttons,
submitFunction, loadedFunction) {
openDialog: function (titleString, msgString, persistent, buttons,
submitFunction, loadedFunction, closeFunction) {
if (!popupEnabled)
return;
var args = {
title: titleString,
persistent: persistent,
buttons: buttons,
defaultButton: 1,
loaded: loadedFunction,
submit: submitFunction
submit: submitFunction,
close: closeFunction
};
if (persistent) {
args.closeText = '';
}
return new Impromptu(msgString, args);
};
},
/**
* Closes currently opened dialog.
*/
my.closeDialog = function () {
closeDialog: function () {
$.prompt.close();
};
},
/**
* Shows a dialog with different states to the user.
*
* @param statesObject object containing all the states of the dialog.
*/
my.openDialogWithStates = function (statesObject, options) {
openDialogWithStates: function (statesObject, options) {
if (!popupEnabled)
return;
return new Impromptu(statesObject, options);
};
},
/**
* Opens new popup window for given <tt>url</tt> centered over current
@@ -146,7 +169,10 @@ var messageHandler = (function(my) {
* @returns {object} popup window object if opened successfully or undefined
* in case we failed to open it(popup blocked)
*/
my.openCenteredPopup = function (url, w, h, onPopupClosed) {
openCenteredPopup: function (url, w, h, onPopupClosed) {
if (!popupEnabled)
return;
var l = window.screenX + (window.innerWidth / 2) - (w / 2);
var t = window.screenY + (window.innerHeight / 2) - (h / 2);
var popup = window.open(
@@ -161,7 +187,7 @@ var messageHandler = (function(my) {
}, 200);
}
return popup;
};
},
/**
* Shows a dialog prompting the user to send an error report.
@@ -170,18 +196,18 @@ var messageHandler = (function(my) {
* @param msgKey the text of the message
* @param error the error that is being reported
*/
my.openReportDialog = function(titleKey, msgKey, error) {
my.openMessageDialog(titleKey, msgKey);
openReportDialog: function(titleKey, msgKey, error) {
this.openMessageDialog(titleKey, msgKey);
console.log(error);
//FIXME send the error to the server
};
},
/**
* Shows an error dialog to the user.
* @param titleKey the title of the message.
* @param msgKey the text of the message.
*/
my.showError = function(titleKey, msgKey) {
showError: function(titleKey, msgKey) {
if (!titleKey) {
titleKey = "dialog.oops";
@@ -190,21 +216,26 @@ var messageHandler = (function(my) {
msgKey = "dialog.defaultError";
}
messageHandler.openMessageDialog(titleKey, msgKey);
};
},
/**
* 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.
*/
my.notify = function(displayName, displayNameKey,
cls, messageKey, messageArguments, options) {
notify: function(displayName, displayNameKey, cls, messageKey,
messageArguments, options) {
if(!notificationsEnabled)
return;
var displayNameSpan = '<span class="nickname" ';
if (displayName) {
displayNameSpan += ">" + UIUtil.escapeHtml(displayName);
@@ -222,31 +253,26 @@ var messageHandler = (function(my) {
APP.translation.translateString(messageKey,
messageArguments) +
'</span>', null, options);
};
},
/**
* Removes the toaster.
* @param toasterElement
*/
my.remove = function(toasterElement) {
remove: function(toasterElement) {
toasterElement.remove();
};
},
/**
* Disables notifications.
* Enables / disables notifications.
*/
my.disableNotifications = function () {
notificationsEnabled = false;
};
enableNotifications: function (enable) {
notificationsEnabled = enable;
},
/**
* Enables notifications.
*/
my.enableNotifications = function () {
notificationsEnabled = true;
};
return my;
}(messageHandler || {}));
enablePopups: function (enable) {
popupEnabled = enable;
}
};
module.exports = messageHandler;

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

@@ -99,7 +99,7 @@ export default {
var newVal = 100 - data.packetLoss.total;
var oldVal = remoteConnectionQuality[id];
remoteConnectionQuality[id] = calculateQuality(newVal, oldVal);
remoteConnectionQuality[id] = calculateQuality(newVal, oldVal || 100);
eventEmitter.emit(
CQEvents.REMOTESTATS_UPDATED, id, remoteConnectionQuality[id],

View File

@@ -0,0 +1,246 @@
/* global $, APP, JitsiMeetJS, config, interfaceConfig */
let currentAudioInputDevices,
currentVideoInputDevices,
currentAudioOutputDevices;
/**
* Determines if currently selected audio output device should be changed after
* list of available devices has been changed.
* @param {MediaDeviceInfo[]} newDevices
* @returns {string|undefined} - ID of new audio output device to use, undefined
* if audio output device should not be changed.
*/
function getNewAudioOutputDevice(newDevices) {
if (!JitsiMeetJS.mediaDevices.isDeviceChangeAvailable('output')) {
return;
}
let selectedAudioOutputDeviceId = APP.settings.getAudioOutputDeviceId();
let availableAudioOutputDevices = newDevices.filter(
d => d.kind === 'audiooutput');
// Switch to 'default' audio output device if we don't have the selected one
// available anymore.
if (selectedAudioOutputDeviceId !== 'default' &&
!availableAudioOutputDevices.find(d =>
d.deviceId === selectedAudioOutputDeviceId)) {
return 'default';
}
}
/**
* Determines if currently selected audio input device should be changed after
* list of available devices has been changed.
* @param {MediaDeviceInfo[]} newDevices
* @param {JitsiLocalTrack} localAudio
* @returns {string|undefined} - ID of new microphone device to use, undefined
* if audio input device should not be changed.
*/
function getNewAudioInputDevice(newDevices, localAudio) {
let availableAudioInputDevices = newDevices.filter(
d => d.kind === 'audioinput');
let selectedAudioInputDeviceId = APP.settings.getMicDeviceId();
let selectedAudioInputDevice = availableAudioInputDevices.find(
d => d.deviceId === selectedAudioInputDeviceId);
// Here we handle case when no device was initially plugged, but
// then it's connected OR new device was connected when previous
// track has ended.
if (!localAudio || localAudio.disposed || localAudio.isEnded()) {
// If we have new audio device and permission to use it was granted
// (label is not an empty string), then we will try to use the first
// available device.
if (availableAudioInputDevices.length &&
availableAudioInputDevices[0].label !== '') {
return availableAudioInputDevices[0].deviceId;
}
// Otherwise we assume that we don't have any audio input devices
// to use and that's why disable microphone button on UI.
else {
APP.UI.disableMicrophoneButton();
}
} else {
// And here we handle case when we already have some device working,
// but we plug-in a "preferred" (previously selected in settings, stored
// in local storage) device.
if (selectedAudioInputDevice &&
selectedAudioInputDeviceId !== localAudio.getDeviceId()) {
return selectedAudioInputDeviceId;
}
}
}
/**
* Determines if currently selected video input device should be changed after
* list of available devices has been changed.
* @param {MediaDeviceInfo[]} newDevices
* @param {JitsiLocalTrack} localVideo
* @returns {string|undefined} - ID of new camera device to use, undefined
* if video input device should not be changed.
*/
function getNewVideoInputDevice(newDevices, localVideo) {
let availableVideoInputDevices = newDevices.filter(
d => d.kind === 'videoinput');
let selectedVideoInputDeviceId = APP.settings.getCameraDeviceId();
let selectedVideoInputDevice = availableVideoInputDevices.find(
d => d.deviceId === selectedVideoInputDeviceId);
// Here we handle case when no video input device was initially plugged,
// but then device is connected OR new device was connected when
// previous track has ended.
if (!localVideo || localVideo.disposed || localVideo.isEnded()) {
// If we have new video device and permission to use it was granted
// (label is not an empty string), then we will try to use the first
// available device.
if (availableVideoInputDevices.length &&
availableVideoInputDevices[0].label !== '') {
return availableVideoInputDevices[0].deviceId;
}
// Otherwise we assume that we don't have any video input devices
// to use and that's why disable microphone button on UI.
else {
APP.UI.disableCameraButton();
}
} else {
// And here we handle case when we already have some device working,
// but we plug-in a "preferred" (previously selected in settings, stored
// in local storage) device.
if (selectedVideoInputDevice &&
selectedVideoInputDeviceId !== localVideo.getDeviceId()) {
return selectedVideoInputDeviceId;
}
}
}
export default {
/**
* Returns list of devices of single kind.
* @param {MediaDeviceInfo[]} devices
* @param {'audioinput'|'audiooutput'|'videoinput'} kind
* @returns {MediaDeviceInfo[]}
*/
getDevicesFromListByKind(devices, kind) {
return devices.filter(d => d.kind === kind);
},
/**
* Stores lists of current 'audioinput', 'videoinput' and 'audiooutput'
* devices.
* @param {MediaDeviceInfo[]} devices
*/
setCurrentMediaDevices(devices) {
currentAudioInputDevices =
this.getDevicesFromListByKind(devices, 'audioinput');
currentVideoInputDevices =
this.getDevicesFromListByKind(devices, 'videoinput');
currentAudioOutputDevices =
this.getDevicesFromListByKind(devices, 'audiooutput');
},
/**
* Returns lists of current 'audioinput', 'videoinput' and 'audiooutput'
* devices.
* @returns {{
* audioinput: (MediaDeviceInfo[]|undefined),
* videoinput: (MediaDeviceInfo[]|undefined),
* audiooutput: (MediaDeviceInfo[]|undefined),
* }}
*/
getCurrentMediaDevices() {
return {
audioinput: currentAudioInputDevices,
videoinput: currentVideoInputDevices,
audiooutput: currentAudioOutputDevices
};
},
/**
* Determines if currently selected media devices should be changed after
* list of available devices has been changed.
* @param {MediaDeviceInfo[]} newDevices
* @param {boolean} isSharingScreen
* @param {JitsiLocalTrack} localVideo
* @param {JitsiLocalTrack} localAudio
* @returns {{
* audioinput: (string|undefined),
* videoinput: (string|undefined),
* audiooutput: (string|undefined)
* }}
*/
getNewMediaDevicesAfterDeviceListChanged(
newDevices, isSharingScreen, localVideo, localAudio) {
return {
audioinput: getNewAudioInputDevice(newDevices, localAudio),
videoinput: !isSharingScreen &&
getNewVideoInputDevice(newDevices, localVideo),
audiooutput: getNewAudioOutputDevice(newDevices)
};
},
/**
* Tries to create new local tracks for new devices obtained after device
* list changed. Shows error dialog in case of failures.
* @param {function} createLocalTracks
* @param {string} (cameraDeviceId)
* @param {string} (micDeviceId)
* @returns {Promise.<JitsiLocalTrack[]>}
*/
createLocalTracksAfterDeviceListChanged(
createLocalTracks, cameraDeviceId, micDeviceId) {
let audioTrackError;
let videoTrackError;
let audioRequested = !!micDeviceId;
let videoRequested = !!cameraDeviceId;
if (audioRequested && videoRequested) {
// First we try to create both audio and video tracks together.
return createLocalTracks({
devices: ['audio', 'video'],
cameraDeviceId: cameraDeviceId,
micDeviceId: micDeviceId
})
// If we fail to do this, try to create them separately.
.catch(() => Promise.all([
createAudioTrack(false).then(([stream]) => stream),
createVideoTrack(false).then(([stream]) => stream)
]))
.then(tracks => {
if (audioTrackError || videoTrackError) {
APP.UI.showDeviceErrorDialog(
audioTrackError, videoTrackError);
}
return tracks.filter(t => typeof t !== 'undefined');
});
} else if (videoRequested && !audioRequested) {
return createVideoTrack();
} else if (audioRequested && !videoRequested) {
return createAudioTrack();
} else {
return Promise.resolve([]);
}
function createAudioTrack(showError) {
return createLocalTracks({
devices: ['audio'],
cameraDeviceId: null,
micDeviceId: micDeviceId
})
.catch(err => {
audioTrackError = err;
showError && APP.UI.showDeviceErrorDialog(err, null);
return [];
});
}
function createVideoTrack(showError) {
return createLocalTracks({
devices: ['video'],
cameraDeviceId: cameraDeviceId,
micDeviceId: null
})
.catch(err => {
videoTrackError = err;
showError && APP.UI.showDeviceErrorDialog(null, err);
return [];
});
}
}
};

View File

@@ -3,6 +3,12 @@
var shortcuts = {};
function initShortcutHandlers() {
shortcuts = {
27: {
character: "Esc",
function: function() {
APP.UI.showKeyboardShortcutsPanel(false);
}
},
67: {
character: "C",
id: "toggleChatPopover",
@@ -31,6 +37,13 @@ function initShortcutHandlers() {
APP.conference.toggleAudioMuted();
}
},
82: {
character: "R",
function: function() {
APP.conference.maybeToggleRaisedHand();
}
},
84: {
character: "T",
function: function() {
@@ -43,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();
}
}
}
};
}
@@ -56,7 +78,7 @@ var KeyboardShortcut = {
$(":focus").is("input[type=password]") ||
$(":focus").is("textarea"))) {
if (typeof shortcuts[keycode] === "object") {
shortcuts[keycode].function();
shortcuts[keycode].function(e);
}
else if (keycode >= "0".charCodeAt(0) &&
keycode <= "9".charCodeAt(0)) {

View File

@@ -9,6 +9,7 @@ let cameraDeviceId = '';
let micDeviceId = '';
let welcomePageDisabled = false;
let localFlipX = null;
let avatarUrl = '';
function supportsLocalStorage() {
try {
@@ -34,6 +35,7 @@ if (supportsLocalStorage()) {
}
email = UIUtil.unescapeHtml(window.localStorage.email || '');
avatarUrl = UIUtil.unescapeHtml(window.localStorage.avatarUrl || '');
localFlipX = JSON.parse(window.localStorage.localFlipX || true);
displayName = UIUtil.unescapeHtml(window.localStorage.displayname || '');
language = window.localStorage.language;
@@ -98,6 +100,24 @@ export default {
return email;
},
/**
* Sets new avatarUrl for local user and saves it to the local storage.
* @param {string} newAvatarUrl new avatarUrl for the local user
*/
setAvatarUrl: function (newAvatarUrl) {
avatarUrl = newAvatarUrl;
window.localStorage.avatarUrl = UIUtil.escapeHtml(newAvatarUrl);
},
/**
* Returns avatarUrl address of the local user.
* @returns {string} avatarUrl
*/
getAvatarUrl: function () {
return avatarUrl;
},
getLanguage () {
return language;
},

View File

@@ -36,7 +36,10 @@
};
navigator.webkitGetUserMedia({
audio: false, video: vid_constraint
}, callback, errorCallback);
}, callback, function (error) {
errorCallback &&
errorCallback(error, vid_constraint);
});
}
);
}

View File

@@ -30,7 +30,9 @@
"retry": "0.6.1",
"strophe": "^1.2.2",
"strophejs-plugins": "^0.0.6",
"toastr": "^2.0.3"
"toastr": "^2.0.3",
"postis": "^2.2.0",
"jws": "*"
},
"devDependencies": {
"browserify": "11.1.x",

View File

@@ -15,6 +15,7 @@ local host = module.host;
local appId = module:get_option_string("app_id");
local appSecret = module:get_option_string("app_secret");
local allowEmptyToken = module:get_option_boolean("allow_empty_token");
local disableRoomNameConstraints = module:get_option_boolean("disable_room_name_constraints");
if allowEmptyToken == true then
module:log("warn", "WARNING - empty tokens allowed");
@@ -79,7 +80,7 @@ function provider.get_sasl_handler(session)
-- here we check if 'room' claim exists
local room, roomErr = token_util.get_room_name(token, appSecret);
if room == nil then
if room == nil and disableRoomNameConstraints ~= true then
if roomErr == nil then
roomErr = "'room' claim is missing";
end
@@ -88,7 +89,7 @@ function provider.get_sasl_handler(session)
-- now verify the whole token
local result, msg
= token_util.verify_token(token, appId, appSecret, room);
= token_util.verify_token(token, appId, appSecret, room, disableRoomNameConstraints);
if result == true then
-- Binds room name to the session which is later checked on MUC join
session.jitsi_meet_room = room;
@@ -121,4 +122,3 @@ local function anonymous(self, message)
end
sasl.registerMechanism("ANONYMOUS", {"anonymous"}, anonymous);

View File

@@ -24,6 +24,7 @@ end
local appId = parentCtx:get_option_string("app_id");
local appSecret = parentCtx:get_option_string("app_secret");
local allowEmptyToken = parentCtx:get_option_boolean("allow_empty_token");
local disableRoomNameConstraints = parentCtx:get_option_boolean("disable_room_name_constraints")
log("debug",
"%s - starting MUC token verifier app_id: %s app_secret: %s allow empty: %s",
@@ -35,13 +36,6 @@ local function verify_user(session, stanza)
tostring(session.auth_token),
tostring(session.jitsi_meet_room));
if allowEmptyToken and session.auth_token == nil then
module:log(
"debug",
"Skipped room token verification - empty tokens are allowed");
return nil;
end
-- token not required for admin users
local user_jid = stanza.attr.from;
if is_admin(user_jid) then
@@ -49,6 +43,13 @@ local function verify_user(session, stanza)
return nil;
end
if allowEmptyToken and session.auth_token == nil then
module:log(
"debug",
"Skipped room token verification - empty tokens are allowed");
return nil;
end
local room = string.match(stanza.attr.to, "^(%w+)@");
log("debug", "Will verify token for user: %s, room: %s ", user_jid, room);
if room == nil then
@@ -59,7 +60,7 @@ local function verify_user(session, stanza)
local token = session.auth_token;
local auth_room = session.jitsi_meet_room;
if room ~= auth_room then
if room ~= auth_room and disableRoomNameConstraints ~= true then
log("error", "Token %s not allowed to join: %s",
tostring(token), tostring(auth_room));
session.send(
@@ -81,4 +82,3 @@ module:hook("muc-occupant-pre-join", function(event)
log("debug", "pre join: %s %s", tostring(room), tostring(stanza));
return verify_user(origin, stanza);
end);

View File

@@ -1,7 +1,7 @@
-- Token authentication
-- Copyright (C) 2015 Atlassian
local jwt = require "luajwt";
local jwt = require "jwt";
local _M = {};
@@ -14,7 +14,7 @@ local function _get_room_name(token, appSecret)
end
end
local function _verify_token(token, appId, appSecret, roomName)
local function _verify_token(token, appId, appSecret, roomName, disableRoomNameConstraints)
local claims, err = jwt.decode(token, appSecret, true);
if claims == nil then
@@ -30,22 +30,22 @@ local function _verify_token(token, appId, appSecret, roomName)
end
local roomClaim = claims["room"];
if roomClaim == nil then
if roomClaim == nil and disableRoomNameConstraints ~= true then
return nil, "'room' claim is missing";
end
if roomName ~= nil and roomName ~= roomClaim then
if roomName ~= nil and roomName ~= roomClaim and disableRoomNameConstraints ~= true then
return nil, "Invalid room name('room' claim)";
end
return true;
end
function _M.verify_token(token, appId, appSecret, roomName)
return _verify_token(token, appId, appSecret, roomName);
function _M.verify_token(token, appId, appSecret, roomName, disableRoomNameConstraints)
return _verify_token(token, appId, appSecret, roomName, disableRoomNameConstraints);
end
function _M.get_room_name(token, appSecret)
return _get_room_name(token, appSecret);
end
return _M;
return _M;

View File

@@ -14,6 +14,10 @@ export default {
* Notifies that local user changed email.
*/
EMAIL_CHANGED: "UI.email_changed",
/**
* Notifies that local user changed avatar url.
*/
AVATAR_URL_CHANGED: "UI.avatar_url_changed",
/**
* Notifies that "start muted" settings changed.
*/

BIN
sounds/ring.ogg Normal file

Binary file not shown.

BIN
sounds/ring.wav Normal file

Binary file not shown.

View File

@@ -36,17 +36,32 @@ function getRoomName () {
}
/**
* Parses the hash parameters from the URL and returns them as a JS object.
* Parses the parameters from the URL and returns them as a JS object.
* @param source {string} values - "hash"/"search" if "search" the parameters
* will parsed from location.search otherwise from location.hash
* @param dontParse if false or undefined some transformations
* (for parsing the value as JSON) are going to be executed
*/
function getConfigParamsFromUrl() {
if (!location.hash)
function getConfigParamsFromUrl(source, dontParse) {
var paramStr = (source === "search")? location.search : location.hash;
if (!paramStr)
return {};
var hash = location.hash.substr(1);
paramStr = paramStr.substr(1);
var result = {};
hash.split("&").forEach(function (part) {
paramStr.split("&").forEach(function (part) {
var item = part.split("=");
result[item[0]] = JSON.parse(
decodeURIComponent(item[1]).replace(/\\&/, "&"));
var value;
try {
value = (dontParse)? item[1] : JSON.parse(
decodeURIComponent(item[1]).replace(/\\&/, "&"));
} catch (e) {
console.warn("Failed to parse URL argument", e);
if(window.onerror)
window.onerror("Failed to parse URL argument", null, null,
null, e);
return;
}
result[item[0]] = value;
});
return result;
}