mirror of
https://gitcode.com/GitHub_Trending/ji/jitsi-meet.git
synced 2026-01-05 06:12:28 +00:00
Compare commits
72 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f39f8d14fd | ||
|
|
770b003163 | ||
|
|
702b177e06 | ||
|
|
3c0295e294 | ||
|
|
970e8c764c | ||
|
|
1d393f5786 | ||
|
|
f03b228eea | ||
|
|
d149ba6fc5 | ||
|
|
f3dc6f15e4 | ||
|
|
8ca282079a | ||
|
|
769644a63f | ||
|
|
2cefea3677 | ||
|
|
2e802c0f6d | ||
|
|
d29e39c1d2 | ||
|
|
09fb5e5667 | ||
|
|
70e5ce7aec | ||
|
|
1f942aa13d | ||
|
|
b60095df28 | ||
|
|
6715d41f92 | ||
|
|
375b145030 | ||
|
|
9d3b2aee02 | ||
|
|
2d2e27b8d0 | ||
|
|
4b6ac38058 | ||
|
|
21c2469dd6 | ||
|
|
02f176c75a | ||
|
|
8b528b582f | ||
|
|
72d38ad202 | ||
|
|
7a5461e1cb | ||
|
|
1714ede6d4 | ||
|
|
f8ee97a71c | ||
|
|
897a6bfbe6 | ||
|
|
97237470af | ||
|
|
d79971a737 | ||
|
|
334f7bf95a | ||
|
|
661795fd51 | ||
|
|
47fe71c1f1 | ||
|
|
c5eebcda98 | ||
|
|
8deb003ef6 | ||
|
|
10b2746a3e | ||
|
|
62fd07e98e | ||
|
|
ee8a270a36 | ||
|
|
870a4e705b | ||
|
|
9dcb717a51 | ||
|
|
f72e3bf552 | ||
|
|
ef70ff7da0 | ||
|
|
61fa2d8ed1 | ||
|
|
1bda4ca61c | ||
|
|
ba00080462 | ||
|
|
57815cb2fe | ||
|
|
346ff889ea | ||
|
|
165507b83a | ||
|
|
955e01a750 | ||
|
|
ca62f9bec2 | ||
|
|
c82bf2a19c | ||
|
|
98919e0996 | ||
|
|
81437263b4 | ||
|
|
f883199f4f | ||
|
|
207e6e1b7d | ||
|
|
06911c4c75 | ||
|
|
fa1ea94c5c | ||
|
|
6b704f184b | ||
|
|
c2eede2bb5 | ||
|
|
0fec9565e5 | ||
|
|
8114152369 | ||
|
|
968521ef7c | ||
|
|
ed9fd6c8fd | ||
|
|
5d9d6b4642 | ||
|
|
f574dbe056 | ||
|
|
ccdba03888 | ||
|
|
e257a3dfc9 | ||
|
|
448fcf36b6 | ||
|
|
48b219111d |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -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
|
||||
|
||||
22
Makefile
22
Makefile
@@ -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
40
app.js
@@ -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();
|
||||
});
|
||||
|
||||
766
conference.js
766
conference.js
@@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
12
css/font.css
12
css/font.css
@@ -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');
|
||||
|
||||
@@ -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;
|
||||
|
||||
19
css/keyboard-shortcuts.css
Normal file
19
css/keyboard-shortcuts.css
Normal 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;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
/*Initialize*/
|
||||
ul.loginmenu {
|
||||
font-family:'Helvetica Neue', Helvetica, sans-serif;
|
||||
font-family: inherit;
|
||||
display:none;
|
||||
position: absolute;
|
||||
margin: 0;
|
||||
|
||||
50
css/main.css
50
css/main.css
@@ -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
66
css/overlay.css
Normal 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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
2
debian/control
vendored
@@ -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
|
||||
|
||||
|
||||
6
debian/jitsi-meet-tokens.postinst
vendored
6
debian/jitsi-meet-tokens.postinst
vendored
@@ -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)
|
||||
|
||||
1
debian/jitsi-meet.postinst
vendored
1
debian/jitsi-meet.postinst
vendored
@@ -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
|
||||
|
||||
37
doc/api.md
37
doc/api.md
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
377
external_api.js
377
external_api.js
@@ -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;
|
||||
|
||||
})();
|
||||
BIN
fonts/OpenSans-Light-webfont.eot
Normal file
BIN
fonts/OpenSans-Light-webfont.eot
Normal file
Binary file not shown.
1831
fonts/OpenSans-Light-webfont.svg
Normal file
1831
fonts/OpenSans-Light-webfont.svg
Normal file
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 114 KiB |
BIN
fonts/OpenSans-Light-webfont.ttf
Normal file
BIN
fonts/OpenSans-Light-webfont.ttf
Normal file
Binary file not shown.
BIN
fonts/OpenSans-Light-webfont.woff
Normal file
BIN
fonts/OpenSans-Light-webfont.woff
Normal file
Binary file not shown.
78
index.html
78
index.html
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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."
|
||||
}
|
||||
}
|
||||
@@ -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":
|
||||
{
|
||||
|
||||
@@ -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
359
modules/API/external/external_api.js
vendored
Normal 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;
|
||||
@@ -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;
|
||||
|
||||
121
modules/TokenData/TokenData.js
Normal file
121
modules/TokenData/TokenData.js
Normal 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;
|
||||
}
|
||||
@@ -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.
|
||||
*
|
||||
|
||||
306
modules/UI/UI.js
306
modules/UI/UI.js
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
82
modules/UI/ring_overlay/RingOverlay.js
Normal file
82
modules/UI/ring_overlay/RingOverlay.js
Normal 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;
|
||||
}
|
||||
};
|
||||
@@ -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>
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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>`,
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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],
|
||||
|
||||
246
modules/devices/mediaDeviceHelper.js
Normal file
246
modules/devices/mediaDeviceHelper.js
Normal 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 [];
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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;
|
||||
},
|
||||
|
||||
@@ -36,7 +36,10 @@
|
||||
};
|
||||
navigator.webkitGetUserMedia({
|
||||
audio: false, video: vid_constraint
|
||||
}, callback, errorCallback);
|
||||
}, callback, function (error) {
|
||||
errorCallback &&
|
||||
errorCallback(error, vid_constraint);
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
BIN
sounds/ring.ogg
Normal file
Binary file not shown.
BIN
sounds/ring.wav
Normal file
BIN
sounds/ring.wav
Normal file
Binary file not shown.
29
utils.js
29
utils.js
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user