Merge pull request #433 from isymchych/jitsi-meet-new

Integration of UI module with lib-jitsi-meet
This commit is contained in:
hristoterezov 2015-12-22 16:56:47 -06:00
commit 6e007a03af
33 changed files with 3682 additions and 3131 deletions

View File

@ -15,5 +15,6 @@
"newcap": true, // true: Require capitalization of all constructor functions e.g. `new F()`
"maxlen": 80, // {int} Max number of characters per line
"latedef": false, //This option prohibits the use of a variable before it was defined
"laxbreak": true //Ignore line breaks around "=", "==", "&&", etc.
"laxbreak": true, //Ignore line breaks around "=", "==", "&&", etc.
"esnext": true //support ES2015
}

501
app.js
View File

@ -1,49 +1,90 @@
/* global $, JitsiMeetJS, config, Promise */
/* global $, JitsiMeetJS, config, interfaceConfig */
/* application specific logic */
require("jquery");
require("jquery-ui");
require("strophe");
require("strophe-disco");
require("strophe-caps");
require("tooltip");
require("popover");
import "babel-polyfill";
import "jquery";
import "jquery-ui";
import "strophe";
import "strophe-disco";
import "strophe-caps";
import "tooltip";
import "popover";
import "jQuery-Impromptu";
import "autosize";
window.toastr = require("toastr");
require("jQuery-Impromptu");
require("autosize");
var CQEvents = require('./service/connectionquality/CQEvents');
var UIEvents = require('./service/UI/UIEvents');
import URLProcessor from "./modules/config/URLProcessor";
import RoomnameGenerator from './modules/util/RoomnameGenerator';
import CQEvents from './service/connectionquality/CQEvents';
import UIEvents from './service/UI/UIEvents';
var Commands = {
import {openConnection} from './modules/connection';
import AuthHandler from './modules/AuthHandler';
import createRoomLocker from './modules/RoomLocker';
const Commands = {
CONNECTION_QUALITY: "connectionQuality",
EMAIL: "email"
};
var APP = {
init: function () {
JitsiMeetJS.init();
JitsiMeetJS.setLogLevel(JitsiMeetJS.logLevels.TRACE);
function buildRoomName () {
let path = window.location.pathname;
let roomName;
// determinde the room node from the url
// TODO: just the roomnode or the whole bare jid?
if (config.getroomnode && typeof config.getroomnode === 'function') {
// custom function might be responsible for doing the pushstate
roomName = config.getroomnode(path);
} else {
/* fall back to default strategy
* this is making assumptions about how the URL->room mapping happens.
* It currently assumes deployment at root, with a rewrite like the
* following one (for nginx):
location ~ ^/([a-zA-Z0-9]+)$ {
rewrite ^/(.*)$ / break;
}
*/
if (path.length > 1) {
roomName = path.substr(1).toLowerCase();
} else {
let word = RoomnameGenerator.generateRoomWithoutSeparator();
roomName = word.toLowerCase();
window.history.pushState(
'VideoChat', `Room: ${word}`, window.location.pathname + word
);
}
}
return roomName;
}
const APP = {
init () {
let roomName = buildRoomName();
this.conference = {
roomName,
localId: undefined,
isModerator: false,
membersCount: 0,
audioMuted: false,
videoMuted: false,
isLocalId: function (id) {
sipGatewayEnabled: false, //FIXME handle
isLocalId (id) {
return this.localId === id;
},
muteAudio: function (mute) {
muteAudio (mute) {
APP.UI.eventEmitter.emit(UIEvents.AUDIO_MUTED, mute);
},
toggleAudioMuted: function () {
toggleAudioMuted () {
this.muteAudio(!this.audioMuted);
},
muteVideo: function (mute) {
muteVideo (mute) {
APP.UI.eventEmitter.emit(UIEvents.VIDEO_MUTED, mute);
},
toggleVideoMuted: function () {
toggleVideoMuted () {
this.muteVideo(!this.videoMuted);
}
};
@ -64,98 +105,116 @@ var APP = {
};
var ConnectionEvents = JitsiMeetJS.events.connection;
var ConnectionErrors = JitsiMeetJS.errors.connection;
function connect() {
var connection = new JitsiMeetJS.JitsiConnection(null, null, {
hosts: config.hosts,
bosh: config.bosh,
clientNode: config.clientNode
});
const ConnectionEvents = JitsiMeetJS.events.connection;
const ConnectionErrors = JitsiMeetJS.errors.connection;
return new Promise(function (resolve, reject) {
var handlers = {};
var unsubscribe = function () {
Object.keys(handlers).forEach(function (event) {
connection.removeEventListener(event, handlers[event]);
});
};
handlers[ConnectionEvents.CONNECTION_ESTABLISHED] = function () {
console.log('CONNECTED');
unsubscribe();
resolve(connection);
};
var listenForFailure = function (event) {
handlers[event] = function () {
// convert arguments to array
var args = Array.prototype.slice.call(arguments);
args.unshift(event);
// [event, ...params]
console.error('CONNECTION FAILED:', args);
unsubscribe();
reject(args);
};
};
listenForFailure(ConnectionEvents.CONNECTION_FAILED);
listenForFailure(ConnectionErrors.PASSWORD_REQUIRED);
listenForFailure(ConnectionErrors.CONNECTION_ERROR);
listenForFailure(ConnectionErrors.OTHER_ERRORS);
// install event listeners
Object.keys(handlers).forEach(function (event) {
connection.addEventListener(event, handlers[event]);
});
connection.connect();
}).catch(function (err) {
if (err[0] === ConnectionErrors.PASSWORD_REQUIRED) {
// FIXME ask for password and try again
return connect();
}
console.error('FAILED TO CONNECT', err);
APP.UI.notifyConnectionFailed(err[1]);
throw new Error(err[0]);
});
}
var ConferenceEvents = JitsiMeetJS.events.conference;
var ConferenceErrors = JitsiMeetJS.errors.conference;
function initConference(connection, roomName) {
var room = connection.initJitsiConference(roomName, {
const ConferenceEvents = JitsiMeetJS.events.conference;
const ConferenceErrors = JitsiMeetJS.errors.conference;
function initConference(localTracks, connection) {
let room = connection.initJitsiConference(APP.conference.roomName, {
openSctp: config.openSctp,
disableAudioLevels: config.disableAudioLevels
});
var users = {};
var localTracks = [];
APP.conference.localId = room.myUserId();
Object.defineProperty(APP.conference, "membersCount", {
get: function () {
return Object.keys(users).length; // FIXME maybe +1?
return room.getParticipants().length; // FIXME maybe +1?
}
});
room.on(ConferenceEvents.USER_JOINED, function (id) {
users[id] = {
displayName: undefined,
tracks: []
};
APP.conference.listMembers = function () {
return room.getParticipants();
};
APP.conference.listMembersIds = function () {
return room.getParticipants().map(p => p.getId());
};
function getDisplayName(id) {
if (APP.conference.isLocalId(id)) {
return APP.settings.getDisplayName();
}
var participant = room.getParticipantById(id);
if (participant && participant.getDisplayName()) {
return participant.getDisplayName();
}
}
// add local streams when joined to the conference
room.on(ConferenceEvents.CONFERENCE_JOINED, function () {
localTracks.forEach(function (track) {
room.addTrack(track);
APP.UI.addLocalStream(track);
});
APP.UI.updateAuthInfo(room.isAuthEnabled(), room.getAuthLogin());
});
room.on(ConferenceEvents.USER_JOINED, function (id, user) {
if (APP.conference.isLocalId(id)) {
return;
}
console.error('USER %s connnected', id);
// FIXME email???
APP.UI.addUser(id);
APP.UI.addUser(id, user.getDisplayName());
});
room.on(ConferenceEvents.USER_LEFT, function (id) {
delete users[id];
APP.UI.removeUser(id);
room.on(ConferenceEvents.USER_LEFT, function (id, user) {
console.error('USER LEFT', id);
APP.UI.removeUser(id, user.getDisplayName());
});
room.on(ConferenceEvents.USER_ROLE_CHANGED, function (id, role) {
if (APP.conference.isLocalId(id)) {
console.info(`My role changed, new role: ${role}`);
APP.conference.isModerator = room.isModerator();
APP.UI.updateLocalRole(room.isModerator());
} else {
var user = room.getParticipantById(id);
if (user) {
APP.UI.updateUserRole(user);
}
}
});
let roomLocker = createRoomLocker(room);
APP.UI.addListener(UIEvents.ROOM_LOCK_CLICKED, function () {
if (room.isModerator()) {
let promise = roomLocker.isLocked
? roomLocker.askToUnlock()
: roomLocker.askToLock();
promise.then(function () {
APP.UI.markRoomLocked(roomLocker.isLocked);
});
} else {
roomLocker.notifyModeratorRequired();
}
});
room.on(ConferenceEvents.TRACK_ADDED, function (track) {
if (track.isLocal()) { // skip local tracks
return;
}
console.error(
'REMOTE %s TRACK', track.getType(), track.getParticipantId()
);
APP.UI.addRemoteStream(track);
});
room.on(ConferenceEvents.TRACK_REMOVED, function (track) {
if (track.isLocal()) { // skip local tracks
return;
}
console.error(
'REMOTE %s TRACK REMOVED', track.getType(), track.getParticipantId()
);
// FIXME handle
});
room.on(ConferenceEvents.TRACK_MUTE_CHANGED, function (track) {
// FIXME handle mute
});
@ -189,13 +248,21 @@ function initConference(connection, roomName) {
});
room.on(ConferenceEvents.CONNECTION_INTERRUPTED, function () {
APP.UI.markVideoInterrupted(true);
});
room.on(ConferenceEvents.CONNECTION_RESTORED, function () {
APP.UI.markVideoInterrupted(false);
});
if (!interfaceConfig.filmStripOnly) {
room.on(ConferenceEvents.CONNECTION_INTERRUPTED, function () {
APP.UI.markVideoInterrupted(true);
});
room.on(ConferenceEvents.CONNECTION_RESTORED, function () {
APP.UI.markVideoInterrupted(false);
});
APP.UI.addListener(UIEvents.MESSAGE_CREATED, function (message) {
room.sendTextMessage(message);
});
room.on(ConferenceEvents.MESSAGE_RECEIVED, function (id, text, ts) {
APP.UI.addMessage(id, getDisplayName(id), text, ts);
});
}
APP.connectionquality.addListener(
CQEvents.LOCALSTATS_UPDATED,
@ -248,26 +315,22 @@ function initConference(connection, roomName) {
APP.UI.setUserAvatar(data.attributes.id, data.value);
});
let nick = APP.settings.getDisplayName();
if (config.useNicks && !nick) {
nick = APP.UI.askForNickname();
APP.settings.setDisplayName(nick);
}
if (nick) {
room.setDisplayName(nick);
}
room.on(ConferenceEvents.DISPLAY_NAME_CHANGED, function (id, displayName) {
APP.UI.changeDisplayName(id, displayName);
});
APP.UI.addListener(UIEvents.NICKNAME_CHANGED, function (nickname) {
APP.settings.setDisplayName(nickname);
room.setDisplayName(nickname);
});
APP.UI.addListener(UIEvents.MESSAGE_CREATED, function (message) {
room.sendTextMessage(message);
});
room.on(ConferenceErrors.PASSWORD_REQUIRED, function () {
// FIXME handle
});
room.on(ConferenceErrors.CONNECTION_ERROR, function () {
// FIXME handle
APP.UI.changeDisplayName(APP.conference.localId, nickname);
});
APP.UI.addListener(
@ -277,37 +340,192 @@ function initConference(connection, roomName) {
}
);
return new Promise(function (resolve, reject) {
room.on(
ConferenceEvents.CONFERENCE_JOINED,
function () {
resolve();
}
APP.UI.addListener(UIEvents.USER_INVITED, function (roomUrl) {
APP.UI.inviteParticipants(
roomUrl,
APP.conference.roomName,
roomLocker.password,
APP.settings.getDisplayName()
);
APP.UI.closeAuthenticationDialog();
if (config.useNicks) {
// FIXME check this
var nick = APP.UI.askForNickname();
});
// call hangup
APP.UI.addListener(UIEvents.HANGUP, function () {
APP.UI.requestFeedback().then(function () {
connection.disconnect();
if (config.enableWelcomePage) {
setTimeout(function() {
window.localStorage.welcomePageDisabled = false;
window.location.pathname = "/";
}, 3000);
}
});
});
// logout
APP.UI.addListener(UIEvents.LOGOUT, function () {
// FIXME handle logout
// APP.xmpp.logout(function (url) {
// if (url) {
// window.location.href = url;
// } else {
// hangup();
// }
// });
});
APP.UI.addListener(UIEvents.SIP_DIAL, function (sipNumber) {
// FIXME add dial
// APP.xmpp.dial(
// sipNumber,
// 'fromnumber',
// APP.conference.roomName,
// roomLocker.password
// );
});
// Starts or stops the recording for the conference.
APP.UI.addListener(UIEvents.RECORDING_TOGGLE, function (predefinedToken) {
// FIXME recording
// APP.xmpp.toggleRecording(function (callback) {
// if (predefinedToken) {
// callback(predefinedToken);
// return;
// }
// APP.UI.requestRecordingToken().then(callback);
// }, APP.UI.updateRecordingState);
});
APP.UI.addListener(UIEvents.TOPIC_CHANGED, function (topic) {
// FIXME handle topic change
// APP.xmpp.setSubject(topic);
// on SUBJECT_CHANGED UI.setSubject(topic);
});
APP.UI.addListener(UIEvents.USER_KICKED, function (id) {
room.kickParticipant(id);
});
room.on(ConferenceEvents.KICKED, function () {
APP.UI.notifyKicked();
// FIXME close
});
APP.UI.addListener(UIEvents.AUTH_CLICKED, function () {
AuthHandler.authenticate(room);
});
APP.UI.addListener(UIEvents.SELECTED_ENDPOINT, function (id) {
room.selectParticipant(id);
});
room.on(ConferenceEvents.DTMF_SUPPORT_CHANGED, function (isDTMFSupported) {
APP.UI.updateDTMFSupport(isDTMFSupported);
});
$(window).bind('beforeunload', function () {
room.leave();
});
return new Promise(function (resolve, reject) {
room.on(ConferenceEvents.CONFERENCE_JOINED, handleConferenceJoined);
room.on(ConferenceEvents.CONFERENCE_FAILED, onConferenceFailed);
let password;
let reconnectTimeout;
function unsubscribe() {
room.off(
ConferenceEvents.CONFERENCE_JOINED, handleConferenceJoined
);
room.off(
ConferenceEvents.CONFERENCE_FAILED, onConferenceFailed
);
if (reconnectTimeout) {
clearTimeout(reconnectTimeout);
}
AuthHandler.closeAuth();
}
room.join();
function handleConferenceJoined() {
unsubscribe();
resolve();
}
function handleConferenceFailed(err) {
unsubscribe();
reject(err);
}
function onConferenceFailed(err, msg = '') {
console.error('CONFERENCE FAILED:', err, msg);
switch (err) {
// room is locked by the password
case ConferenceErrors.PASSWORD_REQUIRED:
APP.UI.markRoomLocked(true);
roomLocker.requirePassword().then(function () {
room.join(roomLocker.password);
});
break;
case ConferenceErrors.CONNECTION_ERROR:
APP.UI.notifyConnectionFailed(msg);
break;
// not enough rights to create conference
case ConferenceErrors.AUTHENTICATION_REQUIRED:
// schedule reconnect to check if someone else created the room
reconnectTimeout = setTimeout(function () {
room.join(password);
}, 5000);
// notify user that auth is required
AuthHandler.requireAuth(APP.conference.roomName);
break;
default:
handleConferenceFailed(err);
}
}
room.join(password);
});
}
function createLocalTracks () {
return JitsiMeetJS.createLocalTracks({
devices: ['audio', 'video']
}).catch(function (err) {
if (err[0] === ConferenceErrors.PASSWORD_REQUIRED) {
// FIXME ask for password and try again
return initConference(connection, roomName);
console.error('failed to create local tracks', err);
return [];
});
}
function connect() {
return openConnection({retry: true}).catch(function (err) {
if (err === ConnectionErrors.PASSWORD_REQUIRED) {
APP.UI.notifyTokenAuthFailed();
} else {
APP.UI.notifyConnectionFailed(err);
}
// FIXME else notify that we cannot conenct to the room
throw new Error(err[0]);
throw err;
});
}
function init() {
connect().then(function (connection) {
return initConference(connection, APP.UI.generateRoomName());
}).then(function () {
APP.UI.start();
APP.UI.start();
JitsiMeetJS.setLogLevel(JitsiMeetJS.logLevels.TRACE);
JitsiMeetJS.init().then(function () {
return Promise.all([createLocalTracks(), connect()]);
}).then(function ([tracks, connection]) {
console.log('initialized with %s local tracks', tracks.length);
return initConference(tracks, connection);
}).then(function () {
APP.UI.initConference();
APP.UI.addListener(UIEvents.LANG_CHANGED, function (language) {
@ -319,6 +537,8 @@ function init() {
APP.statistics.start();
APP.connectionquality.init();
APP.keyboardshortcut.init();
}).catch(function (err) {
console.error(err);
});
}
@ -331,7 +551,7 @@ function init() {
* will be displayed to the user.
*/
function obtainConfigAndInit() {
var roomName = APP.UI.getRoomNode();
let roomName = APP.conference.roomName;
if (config.configLocation) {
APP.configFetch.obtainConfig(
@ -361,7 +581,6 @@ function obtainConfigAndInit() {
$(document).ready(function () {
console.log("(TIME) document ready:\t", window.performance.now());
var URLProcessor = require("./modules/config/URLProcessor");
URLProcessor.setConfigParametersFromUrl();
APP.init();

File diff suppressed because it is too large Load Diff

108
modules/AuthHandler.js Normal file
View File

@ -0,0 +1,108 @@
/* global JitsiMeetJS */
import LoginDialog from './UI/authentication/LoginDialog';
import UIEvents from '../service/UI/UIEvents';
import UIUtil from './UI/util/UIUtil';
import {openConnection} from './connection';
const ConferenceEvents = JitsiMeetJS.events.conference;
let externalAuthWindow;
let authRequiredDialog;
function doExternalAuth (room, lockPassword) {
if (externalAuthWindow) {
externalAuthWindow.focus();
return;
}
if (room.isJoined()) {
room.getExternalAuthUrl(true).then(function (url) {
externalAuthWindow = LoginDialog.showExternalAuthDialog(
url,
function () {
externalAuthWindow = null;
room.join(lockPassword);
}
);
});
} else {
// If conference has not been started yet
// then redirect to login page
room.getExternalAuthUrl().then(UIUtil.redirect);
}
}
function doXmppAuth (room, lockPassword) {
let loginDialog = LoginDialog.showAuthDialog(function (id, password) {
// auth "on the fly":
// 1. open new connection with proper id and password
// 2. connect to the room
// (this will store sessionId in the localStorage)
// 3. close new connection
// 4. reallocate focus in current room
openConnection({id, password}).then(function (connection) {
// open room
let newRoom = connection.initJitsiConference(room.getName());
newRoom.on(ConferenceEvents.CONFERENCE_FAILED, function (err) {
connection.disconnect();
loginDialog.displayError(err);
});
newRoom.room.moderator.allocateConferenceFocus(function () {
connection.disconnect();
loginDialog.close();
if (room.isJoined()) {
// just reallocate focus if already joined
room.room.moderator.allocateConferenceFocus();
} else {
// or join
room.join(lockPassword);
}
});
}, function (err) {
loginDialog.displayError(err);
});
}, function () { // user canceled
loginDialog.close();
});
}
function authenticate (room, lockPassword) {
if (room.isExternalAuthEnabled()) {
doExternalAuth(room, lockPassword);
} else {
doXmppAuth();
}
}
function requireAuth(roomName) {
if (authRequiredDialog) {
return;
}
authRequiredDialog = LoginDialog.showAuthRequiredDialog(
roomName, authenticate
);
}
function closeAuth() {
if (externalAuthWindow) {
externalAuthWindow.close();
externalAuthWindow = null;
}
if (authRequiredDialog) {
authRequiredDialog.close();
authRequiredDialog = null;
}
}
export default {
authenticate,
requireAuth,
closeAuth
};

143
modules/RoomLocker.js Normal file
View File

@ -0,0 +1,143 @@
/* global APP, JitsiMeetJS */
import messageHandler from './UI/util/MessageHandler';
import UIUtil from './UI/util/UIUtil';
import AnalyticsAdapter from './statistics/AnalyticsAdapter';
function askForNewPassword () {
let passMsg = APP.translation.generateTranslationHTML("dialog.passwordMsg");
let yourPassMsg = APP.translation.translateString("dialog.yourPassword");
let msg = `
<h2>${passMsg}</h2>
<input name="lockKey" type="text"
data-i18n="[placeholder]dialog.yourPassword"
placeholder="${yourPassMsg}" autofocus>
`;
return new Promise(function (resolve, reject) {
messageHandler.openTwoButtonDialog(
null, null, null,
msg, false, "dialog.Save",
function (e, v, m, f) {
if (v && f.lockKey) {
resolve(UIUtil.escapeHtml(f.lockKey));
} else {
reject();
}
},
null, null, 'input:first'
);
});
}
function askForPassword () {
let passRequiredMsg = APP.translation.translateString(
"dialog.passwordRequired"
);
let passMsg = APP.translation.translateString("dialog.password");
let msg = `
<h2 data-i18n="dialog.passwordRequired">${passRequiredMsg}</h2>
<input name="lockKey" type="text"
data-i18n="[placeholder]dialog.password"
placeholder="${passMsg}" autofocus>
`;
return new Promise(function (resolve, reject) {
messageHandler.openTwoButtonDialog(
null, null, null, msg,
true, "dialog.Ok",
function (e, v, m, f) {}, null,
function (e, v, m, f) {
if (v && f.lockKey) {
resolve(UIUtil.escapeHtml(f.lockKey));
} else {
reject();
}
},
':input:first'
);
});
}
function askToUnlock () {
return new Promise(function (resolve, reject) {
messageHandler.openTwoButtonDialog(
null, null, "dialog.passwordCheck",
null, false, "dialog.Remove",
function (e, v) {
if (v) {
resolve();
} else {
reject();
}
}
);
});
}
function notifyPasswordNotSupported (err) {
console.warn('setting password failed', err);
messageHandler.showError("dialog.warning", "dialog.passwordNotSupported");
}
function notifyPasswordFailed() {
console.warn('room passwords not supported');
messageHandler.showError("dialog.lockTitle", "dialog.lockMessage");
}
const ConferenceErrors = JitsiMeetJS.errors.conference;
export default function createRoomLocker (room) {
let password;
function lock (newPass) {
return room.lock(newPass).then(function () {
password = newPass;
}).catch(function (err) {
if (err === ConferenceErrors.PASSWORD_NOT_SUPPORTED) {
notifyPasswordNotSupported();
} else {
notifyPasswordFailed(err);
}
throw err;
});
}
return {
get isLocked () {
return !!password;
},
get password () {
return password;
},
askToUnlock () {
askToUnlock().then(function () {
return lock();
}).then(function () {
AnalyticsAdapter.sendEvent('toolbar.lock.disabled');
});
},
askToLock () {
return askForNewPassword().then(function (newPass) {
return lock(newPass);
}).then(function () {
AnalyticsAdapter.sendEvent('toolbar.lock.enabled');
});
},
requirePassword () {
return askForPassword().then(function (newPass) {
password = newPass;
});
},
notifyModeratorRequired () {
if (password) {
messageHandler.openMessageDialog(null, "dialog.passwordError");
} else {
messageHandler.openMessageDialog(null, "dialog.passwordError2");
}
}
};
}

View File

@ -1,62 +1,58 @@
/* global Strophe, APP, $, config, interfaceConfig, toastr */
/* global APP, $, config, interfaceConfig, toastr */
/* jshint -W101 */
var UI = {};
var VideoLayout = require("./videolayout/VideoLayout.js");
var AudioLevels = require("./audio_levels/AudioLevels.js");
var Prezi = require("./prezi/Prezi.js");
var Etherpad = require("./etherpad/Etherpad.js");
var Chat = require("./side_pannels/chat/Chat.js");
var Toolbar = require("./toolbars/Toolbar");
var ToolbarToggler = require("./toolbars/ToolbarToggler");
var BottomToolbar = require("./toolbars/BottomToolbar");
var ContactList = require("./side_pannels/contactlist/ContactList");
var Avatar = require("./avatar/Avatar");
import AudioLevels from './audio_levels/AudioLevels';
import Chat from "./side_pannels/chat/Chat";
import Toolbar from "./toolbars/Toolbar";
import ToolbarToggler from "./toolbars/ToolbarToggler";
import BottomToolbar from "./toolbars/BottomToolbar";
import ContactList from "./side_pannels/contactlist/ContactList";
import Avatar from "./avatar/Avatar";
import PanelToggler from "./side_pannels/SidePanelToggler";
import UIUtil from "./util/UIUtil";
import UIEvents from "../../service/UI/UIEvents";
import VideoLayout from "./videolayout/VideoLayout";
import SettingsMenu from "./side_pannels/settings/SettingsMenu";
var Prezi = require("./prezi/Prezi");
var Etherpad = require("./etherpad/Etherpad");
var EventEmitter = require("events");
var SettingsMenu = require("./side_pannels/settings/SettingsMenu");
var Settings = require("./../settings/Settings");
var PanelToggler = require("./side_pannels/SidePanelToggler");
var RoomnameGenerator = require("../util/RoomnameGenerator");
UI.messageHandler = require("./util/MessageHandler");
var messageHandler = UI.messageHandler;
var Authentication = require("./authentication/Authentication");
var UIUtil = require("./util/UIUtil");
var JitsiPopover = require("./util/JitsiPopover");
var CQEvents = require("../../service/connectionquality/CQEvents");
var DesktopSharingEventTypes
= require("../../service/desktopsharing/DesktopSharingEventTypes");
var StatisticsEvents = require("../../service/statistics/Events");
var UIEvents = require("../../service/UI/UIEvents");
var Feedback = require("./Feedback");
var eventEmitter = new EventEmitter();
UI.eventEmitter = eventEmitter;
var roomNode = null;
var roomName = null;
function promptDisplayName() {
var message = '<h2 data-i18n="dialog.displayNameRequired">';
message += APP.translation.translateString(
"dialog.displayNameRequired");
message += '</h2>' +
'<input name="displayName" type="text" data-i18n=' +
'"[placeholder]defaultNickname" placeholder="' +
APP.translation.translateString(
"defaultNickname", {name: "Jane Pink"}) +
'" autofocus>';
let nickRequiredMsg = APP.translation.translateString("dialog.displayNameRequired");
let defaultNickMsg = APP.translation.translateString(
"defaultNickname", {name: "Jane Pink"}
);
let message = `
<h2 data-i18n="dialog.displayNameRequired">${nickRequiredMsg}</h2>
<input name="displayName" type="text"
data-i18n="[placeholder]defaultNickname"
placeholder="${defaultNickMsg}" autofocus>`;
var buttonTxt
= APP.translation.generateTranslationHTML("dialog.Ok");
var buttons = [];
buttons.push({title: buttonTxt, value: "ok"});
let buttonTxt = APP.translation.generateTranslationHTML("dialog.Ok");
let buttons = [{title: buttonTxt, value: "ok"}];
messageHandler.openDialog(null, message,
messageHandler.openDialog(
null, message,
true,
buttons,
function (e, v, m, f) {
if (v == "ok") {
var displayName = f.displayName;
let displayName = f.displayName;
if (displayName) {
UI.inputDisplayNameHandler(displayName);
return true;
@ -65,16 +61,17 @@ function promptDisplayName() {
e.preventDefault();
},
function () {
var form = $.prompt.getPrompt();
var input = form.find("input[name='displayName']");
let form = $.prompt.getPrompt();
let input = form.find("input[name='displayName']");
input.focus();
var button = form.find("button");
let button = form.find("button");
button.attr("disabled", "disabled");
input.keyup(function () {
if(!input.val())
button.attr("disabled", "disabled");
else
if (input.val()) {
button.removeAttr("disabled");
} else {
button.attr("disabled", "disabled");
}
});
}
);
@ -99,6 +96,30 @@ function setupToolbars() {
BottomToolbar.init(eventEmitter);
}
/**
* Toggles the application in and out of full screen mode
* (a.k.a. presentation mode in Chrome).
*/
function toggleFullScreen () {
let fsElement = document.documentElement;
if (!document.mozFullScreen && !document.webkitIsFullScreen) {
//Enter Full Screen
if (fsElement.mozRequestFullScreen) {
fsElement.mozRequestFullScreen();
} else {
fsElement.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT);
}
} else {
//Exit Full Screen
if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else {
document.webkitCancelFullScreen();
}
}
}
UI.notifyGracefulShudown = function () {
messageHandler.openMessageDialog(
'dialog.serviceUnavailable',
@ -133,6 +154,10 @@ UI.changeDisplayName = function (id, displayName) {
ContactList.onDisplayNameChange(id, displayName);
SettingsMenu.onDisplayNameChange(id, displayName);
VideoLayout.onDisplayNameChanged(id, displayName);
if (APP.conference.isLocalId(id)) {
Chat.setChatConversationMode(!!displayName);
}
};
UI.initConference = function () {
@ -168,18 +193,34 @@ function registerListeners() {
AudioLevels.init();
});
UI.addListener(UIEvents.FILM_STRIP_TOGGLED, function (isToggled) {
VideoLayout.onFilmStripToggled(isToggled);
});
UI.addListener(UIEvents.EMAIL_CHANGED, function (email) {
UI.setUserAvatar(APP.conference.localId, email);
});
UI.addListener(UIEvents.PREZI_CLICKED, function () {
Prezi.openPreziDialog();
});
UI.addListener(UIEvents.ETHERPAD_CLICKED, function () {
Etherpad.toggleEtherpad(0);
});
UI.addListener(UIEvents.FULLSCREEN_TOGGLE, toggleFullScreen);
UI.addListener(UIEvents.TOGGLE_CHAT, UI.toggleChat);
UI.addListener(UIEvents.TOGGLE_SETTINGS, function () {
PanelToggler.toggleSettingsMenu();
});
UI.addListener(UIEvents.TOGGLE_CONTACT_LIST, UI.toggleContactList);
UI.addListener(UIEvents.TOGGLE_FILM_STRIP, UI.toggleFilmStrip);
}
function bindEvents() {
function onResize() {
Chat.resizeChat();
PanelToggler.resizeChat();
VideoLayout.resizeLargeVideoContainer();
}
@ -213,6 +254,7 @@ UI.start = function () {
registerListeners();
VideoLayout.init(eventEmitter);
ContactList.init(eventEmitter);
bindEvents();
setupPrezi();
@ -281,16 +323,16 @@ UI.start = function () {
};
UI.addLocalStream = function (stream, isMuted) {
switch (stream.type) {
UI.addLocalStream = function (track) {
switch (track.getType()) {
case 'audio':
VideoLayout.changeLocalAudio(stream, isMuted);
VideoLayout.changeLocalAudio(track);
break;
case 'video':
VideoLayout.changeLocalVideo(stream, isMuted);
VideoLayout.changeLocalVideo(track);
break;
default:
console.error("Unknown stream type: " + stream.type);
console.error("Unknown stream type: " + track.getType());
break;
}
};
@ -304,83 +346,17 @@ function chatAddError(errorMessage, originalText) {
return Chat.chatAddError(errorMessage, originalText);
}
function chatSetSubject(text) {
return Chat.chatSetSubject(text);
}
function updateChatConversation(from, displayName, message, stamp) {
return Chat.updateChatConversation(from, displayName, message, stamp);
}
UI.setSubject = function (subject) {
Chat.setSubject(subject);
};
function initEtherpad(name) {
Etherpad.init(name);
}
function onLocalRoleChanged(jid, info, pres, isModerator) {
console.info("My role changed, new role: " + info.role);
onModeratorStatusChanged(isModerator);
VideoLayout.showModeratorIndicator();
SettingsMenu.onRoleChanged();
UI.addUser = function (id, displayName) {
ContactList.addContact(id);
if (isModerator) {
Authentication.closeAuthenticationWindow();
messageHandler.notify(null, "notify.me",
'connected', "notify.moderator");
Toolbar.checkAutoRecord();
}
}
function onModeratorStatusChanged(isModerator) {
Toolbar.showSipCallButton(isModerator);
Toolbar.showRecordingButton(
isModerator); //&&
// FIXME:
// Recording visible if
// there are at least 2(+ 1 focus) participants
//Object.keys(connection.emuc.members).length >= 3);
}
UI.notifyPasswordRequired = function (callback) {
// password is required
Toolbar.lockLockButton();
var message = '<h2 data-i18n="dialog.passwordRequired">';
message += APP.translation.translateString(
"dialog.passwordRequired");
message += '</h2>' +
'<input name="lockKey" type="text" data-i18n=' +
'"[placeholder]dialog.password" placeholder="' +
APP.translation.translateString("dialog.password") +
'" autofocus>';
messageHandler.openTwoButtonDialog(null, null, null, message,
true,
"dialog.Ok",
function (e, v, m, f) {},
null,
function (e, v, m, f) {
if (v) {
var lockKey = f.lockKey;
if (lockKey) {
Toolbar.setSharedKey(lockKey);
callback(lockKey);
}
}
},
':input:first'
);
};
/**
* The dialpad button is shown iff there is at least one member that supports
* DTMF (e.g. jigasi).
*/
function onDtmfSupportChanged(dtmfSupport) {
//TODO: enable when the UI is ready
//Toolbar.showDialPadButton(dtmfSupport);
}
UI.addUser = function (jid, id, displayName) {
messageHandler.notify(
displayName,'notify.somebody', 'connected', 'notify.connected'
);
@ -390,27 +366,24 @@ UI.addUser = function (jid, id, displayName) {
UIUtil.playSoundNotification('userJoined');
// Configure avatar
UI.setUserAvatar(jid, id);
UI.setUserAvatar(id, displayName);
// Add Peer's container
VideoLayout.ensurePeerContainerExists(jid);
VideoLayout.addParticipantContainer(id);
};
UI.removeUser = function (jid) {
console.log('left.muc', jid);
var displayName = $('#participant_' + Strophe.getResourceFromJid(jid) +
'>.displayname').html();
messageHandler.notify(displayName,'notify.somebody',
'disconnected',
'notify.disconnected');
if (!config.startAudioMuted ||
config.startAudioMuted > APP.conference.membersCount) {
UI.removeUser = function (id, displayName) {
ContactList.removeContact(id);
messageHandler.notify(
displayName,'notify.somebody', 'disconnected', 'notify.disconnected'
);
if (!config.startAudioMuted || config.startAudioMuted > APP.conference.membersCount) {
UIUtil.playSoundNotification('userLeft');
}
ContactList.removeContact(jid);
VideoLayout.participantLeft(jid);
VideoLayout.removeParticipantContainer(id);
};
function onMucPresenceStatus(jid, info) {
@ -421,31 +394,41 @@ function onPeerVideoTypeChanged(resourceJid, newVideoType) {
VideoLayout.onVideoTypeChanged(resourceJid, newVideoType);
}
function onMucRoleChanged(role, displayName) {
UI.updateLocalRole = function (isModerator) {
VideoLayout.showModeratorIndicator();
if (role === 'moderator') {
var messageKey, messageOptions = {};
if (!displayName) {
messageKey = "notify.grantedToUnknown";
}
else {
messageKey = "notify.grantedTo";
messageOptions = {to: displayName};
}
messageHandler.notify(
displayName,'notify.somebody',
'connected', messageKey,
messageOptions);
}
}
Toolbar.showSipCallButton(isModerator);
Toolbar.showRecordingButton(isModerator);
SettingsMenu.onRoleChanged();
UI.notifyAuthRequired = function (intervalCallback) {
Authentication.openAuthenticationDialog(
roomName, intervalCallback, function () {
Toolbar.authenticateClicked();
}
);
if (isModerator) {
messageHandler.notify(null, "notify.me", 'connected', "notify.moderator");
Toolbar.checkAutoRecord();
}
};
UI.updateUserRole = function (user) {
VideoLayout.showModeratorIndicator();
if (!user.isModerator()) {
return;
}
var displayName = user.getDisplayName();
if (displayName) {
messageHandler.notify(
displayName, 'notify.somebody',
'connected', 'notify.grantedTo', {
to: displayName
}
);
} else {
messageHandler.notify(
'', 'notify.somebody',
'connected', 'notify.grantedToUnknown', {}
);
}
};
@ -458,15 +441,16 @@ UI.getSettings = function () {
};
UI.toggleFilmStrip = function () {
return BottomToolbar.toggleFilmStrip();
BottomToolbar.toggleFilmStrip();
VideoLayout.updateLargeVideoSize();
};
UI.toggleChat = function () {
return BottomToolbar.toggleChat();
PanelToggler.toggleChat();
};
UI.toggleContactList = function () {
return BottomToolbar.toggleContactList();
PanelToggler.toggleContactList();
};
UI.inputDisplayNameHandler = function (value) {
@ -482,46 +466,6 @@ UI.getRemoteVideoType = function (jid) {
return VideoLayout.getRemoteVideoType(jid);
};
UI.getRoomNode = function () {
if (roomNode)
return roomNode;
var path = window.location.pathname;
// determinde the room node from the url
// TODO: just the roomnode or the whole bare jid?
if (config.getroomnode && typeof config.getroomnode === 'function') {
// custom function might be responsible for doing the pushstate
roomNode = config.getroomnode(path);
} else {
/* fall back to default strategy
* this is making assumptions about how the URL->room mapping happens.
* It currently assumes deployment at root, with a rewrite like the
* following one (for nginx):
location ~ ^/([a-zA-Z0-9]+)$ {
rewrite ^/(.*)$ / break;
}
*/
if (path.length > 1) {
roomNode = path.substr(1).toLowerCase();
} else {
var word = RoomnameGenerator.generateRoomWithoutSeparator();
roomNode = word.toLowerCase();
window.history.pushState('VideoChat',
'Room: ' + word, window.location.pathname + word);
}
}
return roomNode;
};
UI.generateRoomName = function () {
if (roomName)
return roomName;
var roomNode = UI.getRoomNode();
roomName = roomNode + '@' + config.hosts.muc;
return roomName;
};
UI.connectionIndicatorShowMore = function(jid) {
return VideoLayout.showMore(jid);
};
@ -552,19 +496,10 @@ UI.showLoginPopup = function(callback) {
);
};
UI.closeAuthenticationDialog = function () {
Authentication.closeAuthenticationDialog();
Authentication.stopInterval();
};
UI.askForNickname = function () {
return window.prompt('Your nickname (optional)');
};
UI.getRoomName = function () {
return roomName;
};
/**
* Sets muted audio state for the local participant.
*/
@ -595,7 +530,7 @@ UI.showToolbar = function () {
//Used by torture
UI.dockToolbar = function (isDock) {
return ToolbarToggler.dockToolbar(isDock);
ToolbarToggler.dockToolbar(isDock);
};
UI.setUserAvatar = function (id, email) {
@ -655,9 +590,9 @@ UI.handleLastNEndpoints = function (ids) {
VideoLayout.onLastNEndpointsChanged(ids, []);
};
UI.setAudioLevel = function (targetJid, lvl) {
UI.setAudioLevel = function (id, lvl) {
AudioLevels.updateAudioLevel(
targetJid, lvl, VideoLayout.getLargeVideoResource()
id, lvl, VideoLayout.getLargeVideoId()
);
};
@ -677,10 +612,6 @@ UI.updateRemoteStats = function (jid, percent, stats) {
VideoLayout.updateConnectionStats(jid, percent, stats);
};
UI.showAuthenticateButton = function (show) {
Toolbar.showAuthenticateButton(show);
};
UI.markVideoInterrupted = function (interrupted) {
if (interrupted) {
VideoLayout.onVideoInterrupted();
@ -689,4 +620,133 @@ UI.markVideoInterrupted = function (interrupted) {
}
};
UI.markRoomLocked = function (locked) {
if (locked) {
Toolbar.lockLockButton();
} else {
Toolbar.unlockLockButton();
}
};
UI.addMessage = function (from, displayName, message, stamp) {
Chat.updateChatConversation(from, displayName, message, stamp);
};
UI.updateDTMFSupport = function (isDTMFSupported) {
//TODO: enable when the UI is ready
//Toolbar.showDialPadButton(dtmfSupport);
};
/**
* Invite participants to conference.
*/
UI.inviteParticipants = function (roomUrl, conferenceName, key, nick) {
let keyText = "";
if (key) {
keyText = APP.translation.translateString(
"email.sharedKey", {sharedKey: key}
);
}
let and = APP.translation.translateString("email.and");
let supportedBrowsers = `Chromium, Google Chrome ${and} Opera`;
let subject = APP.translation.translateString(
"email.subject", {appName:interfaceConfig.APP_NAME, conferenceName}
);
let body = APP.translation.translateString(
"email.body", {
appName:interfaceConfig.APP_NAME,
sharedKeyText: keyText,
roomUrl,
supportedBrowsers
}
);
body = body.replace(/\n/g, "%0D%0A");
if (nick) {
body += "%0D%0A%0D%0A" + nick;
}
if (interfaceConfig.INVITATION_POWERED_BY) {
body += "%0D%0A%0D%0A--%0D%0Apowered by jitsi.org";
}
window.open(`mailto:?subject=${subject}&body=${body}`, '_blank');
};
UI.requestFeedback = function () {
return new Promise(function (resolve, reject) {
if (Feedback.isEnabled()) {
// If the user has already entered feedback, we'll show the window and
// immidiately start the conference dispose timeout.
if (Feedback.feedbackScore > 0) {
Feedback.openFeedbackWindow();
resolve();
} else { // Otherwise we'll wait for user's feedback.
Feedback.openFeedbackWindow(resolve);
}
} else {
// If the feedback functionality isn't enabled we show a thank you
// dialog.
messageHandler.openMessageDialog(
null, null, null,
APP.translation.translateString(
"dialog.thankYou", {appName:interfaceConfig.APP_NAME}
)
);
resolve();
}
});
};
UI.requestRecordingToken = function () {
let msg = APP.translation.generateTranslationHTML("dialog.recordingToken");
let token = APP.translation.translateString("dialog.token");
return new Promise(function (resolve, reject) {
messageHandler.openTwoButtonDialog(
null, null, null,
`<h2>${msg}</h2>
<input name="recordingToken" type="text"
data-i18n="[placeholder]dialog.token"
placeholder="${token}" autofocus>`,
false, "dialog.Save",
function (e, v, m, f) {
if (v && f.recordingToken) {
resolve(UIUtil.escapeHtml(f.recordingToken));
} else {
reject();
}
},
null,
function () { },
':input:first'
);
});
};
UI.updateRecordingState = function (state) {
Toolbar.updateRecordingState(state);
};
UI.notifyTokenAuthFailed = function () {
messageHandler.showError("dialog.error", "dialog.tokenAuthFailed");
};
UI.updateAuthInfo = function (isAuthEnabled, login) {
let loggedIn = !!login;
Toolbar.showAuthenticateButton(isAuthEnabled);
if (isAuthEnabled) {
Toolbar.setAuthenticatedIdentity(login);
Toolbar.showLoginButton(!loggedIn);
Toolbar.showLogoutButton(loggedIn);
}
};
module.exports = UI;

View File

@ -1,12 +1,16 @@
/* global APP, interfaceConfig, $, Strophe */
/* global APP, interfaceConfig, $ */
/* jshint -W101 */
var CanvasUtil = require("./CanvasUtils");
var ASDrawContext = null;
import CanvasUtil from './CanvasUtils';
const LOCAL_LEVEL = 'local';
let ASDrawContext = null;
let audioLevelCanvasCache = {};
function initActiveSpeakerAudioLevels() {
var ASRadius = interfaceConfig.ACTIVE_SPEAKER_AVATAR_SIZE / 2;
var ASCenter = (interfaceConfig.ACTIVE_SPEAKER_AVATAR_SIZE + ASRadius) / 2;
let ASRadius = interfaceConfig.ACTIVE_SPEAKER_AVATAR_SIZE / 2;
let ASCenter = (interfaceConfig.ACTIVE_SPEAKER_AVATAR_SIZE + ASRadius) / 2;
// Draw a circle.
ASDrawContext.arc(ASCenter, ASCenter, ASRadius, 0, 2 * Math.PI);
@ -18,249 +22,233 @@ function initActiveSpeakerAudioLevels() {
}
/**
* The audio Levels plugin.
* Resizes the given audio level canvas to match the given thumbnail size.
*/
var AudioLevels = (function(my) {
var audioLevelCanvasCache = {};
function resizeAudioLevelCanvas(audioLevelCanvas, thumbnailWidth, thumbnailHeight) {
audioLevelCanvas.width = thumbnailWidth + interfaceConfig.CANVAS_EXTRA;
audioLevelCanvas.height = thumbnailHeight + interfaceConfig.CANVAS_EXTRA;
}
my.LOCAL_LEVEL = 'local';
/**
* Draws the audio level canvas into the cached canvas object.
*
* @param id of the user for whom we draw the audio level
* @param audioLevel the newAudio level to render
*/
function drawAudioLevelCanvas(id, audioLevel) {
if (!audioLevelCanvasCache[id]) {
my.init = function () {
ASDrawContext = $('#activeSpeakerAudioLevel')[0].getContext('2d');
initActiveSpeakerAudioLevels();
};
let videoSpanId = getVideoSpanId(id);
/**
* Updates the audio level canvas for the given peerJid. If the canvas
* didn't exist we create it.
*/
my.updateAudioLevelCanvas = function (peerJid, VideoLayout) {
var resourceJid = null;
var videoSpanId = null;
if (!peerJid)
videoSpanId = 'localVideoContainer';
else {
resourceJid = Strophe.getResourceFromJid(peerJid);
let audioLevelCanvasOrig = $(`#${videoSpanId}>canvas`).get(0);
videoSpanId = 'participant_' + resourceJid;
/*
* FIXME Testing has shown that audioLevelCanvasOrig may not exist.
* In such a case, the method CanvasUtil.cloneCanvas may throw an
* error. Since audio levels are frequently updated, the errors have
* been observed to pile into the console, strain the CPU.
*/
if (audioLevelCanvasOrig) {
audioLevelCanvasCache[id] = CanvasUtil.cloneCanvas(audioLevelCanvasOrig);
}
}
let canvas = audioLevelCanvasCache[id];
if (!canvas) {
return;
}
let drawContext = canvas.getContext('2d');
drawContext.clearRect(0, 0, canvas.width, canvas.height);
let shadowLevel = getShadowLevel(audioLevel);
if (shadowLevel > 0) {
// drawContext, x, y, w, h, r, shadowColor, shadowLevel
CanvasUtil.drawRoundRectGlow(drawContext,
interfaceConfig.CANVAS_EXTRA / 2, interfaceConfig.CANVAS_EXTRA / 2,
canvas.width - interfaceConfig.CANVAS_EXTRA,
canvas.height - interfaceConfig.CANVAS_EXTRA,
interfaceConfig.CANVAS_RADIUS,
interfaceConfig.SHADOW_COLOR,
shadowLevel);
}
}
/**
* Returns the shadow/glow level for the given audio level.
*
* @param audioLevel the audio level from which we determine the shadow
* level
*/
function getShadowLevel (audioLevel) {
let shadowLevel = 0;
if (audioLevel <= 0.3) {
shadowLevel = Math.round(interfaceConfig.CANVAS_EXTRA/2*(audioLevel/0.3));
} else if (audioLevel <= 0.6) {
shadowLevel = Math.round(interfaceConfig.CANVAS_EXTRA/2*((audioLevel - 0.3) / 0.3));
} else {
shadowLevel = Math.round(interfaceConfig.CANVAS_EXTRA/2*((audioLevel - 0.6) / 0.4));
}
return shadowLevel;
}
/**
* Returns the video span id corresponding to the given user id
*/
function getVideoSpanId(id) {
let videoSpanId = null;
if (id === LOCAL_LEVEL || APP.conference.isLocalId(id)) {
videoSpanId = 'localVideoContainer';
} else {
videoSpanId = `participant_${id}`;
}
return videoSpanId;
}
/**
* Indicates that the remote video has been resized.
*/
$(document).bind('remotevideo.resized', function (event, width, height) {
let resized = false;
$('#remoteVideos>span>canvas').each(function() {
let canvas = $(this).get(0);
if (canvas.width !== width + interfaceConfig.CANVAS_EXTRA) {
canvas.width = width + interfaceConfig.CANVAS_EXTRA;
resized = true;
}
var videoSpan = document.getElementById(videoSpanId);
if (canvas.height !== height + interfaceConfig.CANVAS_EXTRA) {
canvas.height = height + interfaceConfig.CANVAS_EXTRA;
resized = true;
}
});
if (resized) {
Object.keys(audioLevelCanvasCache).forEach(function (id) {
audioLevelCanvasCache[id].width = width + interfaceConfig.CANVAS_EXTRA;
audioLevelCanvasCache[id].height = height + interfaceConfig.CANVAS_EXTRA;
});
}
});
/**
* The audio Levels plugin.
*/
const AudioLevels = {
init () {
ASDrawContext = $('#activeSpeakerAudioLevel')[0].getContext('2d');
initActiveSpeakerAudioLevels();
},
/**
* Updates the audio level canvas for the given id. If the canvas
* didn't exist we create it.
*/
updateAudioLevelCanvas (id, VideoLayout) {
let videoSpanId = 'localVideoContainer';
if (id) {
videoSpanId = `participant_${id}`;
}
let videoSpan = document.getElementById(videoSpanId);
if (!videoSpan) {
if (resourceJid)
console.error("No video element for jid", resourceJid);
else
if (id) {
console.error("No video element for id", id);
} else {
console.error("No video element for local video.");
}
return;
}
var audioLevelCanvas = $('#' + videoSpanId + '>canvas');
let audioLevelCanvas = $(`#${videoSpanId}>canvas`);
var videoSpaceWidth = $('#remoteVideos').width();
var thumbnailSize = VideoLayout.calculateThumbnailSize(videoSpaceWidth);
var thumbnailWidth = thumbnailSize[0];
var thumbnailHeight = thumbnailSize[1];
let videoSpaceWidth = $('#remoteVideos').width();
let thumbnailSize = VideoLayout.calculateThumbnailSize(videoSpaceWidth);
let thumbnailWidth = thumbnailSize[0];
let thumbnailHeight = thumbnailSize[1];
if (!audioLevelCanvas || audioLevelCanvas.length === 0) {
audioLevelCanvas = document.createElement('canvas');
audioLevelCanvas.className = "audiolevel";
audioLevelCanvas.style.bottom = "-" + interfaceConfig.CANVAS_EXTRA/2 + "px";
audioLevelCanvas.style.left = "-" + interfaceConfig.CANVAS_EXTRA/2 + "px";
resizeAudioLevelCanvas( audioLevelCanvas,
thumbnailWidth,
thumbnailHeight);
audioLevelCanvas.style.bottom = `-${interfaceConfig.CANVAS_EXTRA/2}px`;
audioLevelCanvas.style.left = `-${interfaceConfig.CANVAS_EXTRA/2}px`;
resizeAudioLevelCanvas(audioLevelCanvas, thumbnailWidth, thumbnailHeight);
videoSpan.appendChild(audioLevelCanvas);
} else {
audioLevelCanvas = audioLevelCanvas.get(0);
resizeAudioLevelCanvas( audioLevelCanvas,
thumbnailWidth,
thumbnailHeight);
resizeAudioLevelCanvas(audioLevelCanvas, thumbnailWidth, thumbnailHeight);
}
};
},
/**
* Updates the audio level UI for the given resourceJid.
* Updates the audio level UI for the given id.
*
* @param resourceJid the resource jid indicating the video element for
* which we draw the audio level
* @param id id of the user for whom we draw the audio level
* @param audioLevel the newAudio level to render
*/
my.updateAudioLevel = function (resourceJid, audioLevel, largeVideoResourceJid) {
drawAudioLevelCanvas(resourceJid, audioLevel);
updateAudioLevel (id, audioLevel, largeVideoId) {
drawAudioLevelCanvas(id, audioLevel);
var videoSpanId = getVideoSpanId(resourceJid);
let videoSpanId = getVideoSpanId(id);
var audioLevelCanvas = $('#' + videoSpanId + '>canvas').get(0);
let audioLevelCanvas = $(`#${videoSpanId}>canvas`).get(0);
if (!audioLevelCanvas)
if (!audioLevelCanvas) {
return;
}
var drawContext = audioLevelCanvas.getContext('2d');
let drawContext = audioLevelCanvas.getContext('2d');
var canvasCache = audioLevelCanvasCache[resourceJid];
let canvasCache = audioLevelCanvasCache[id];
drawContext.clearRect (0, 0,
audioLevelCanvas.width, audioLevelCanvas.height);
drawContext.clearRect(
0, 0, audioLevelCanvas.width, audioLevelCanvas.height
);
drawContext.drawImage(canvasCache, 0, 0);
if(resourceJid === AudioLevels.LOCAL_LEVEL) {
resourceJid = APP.conference.localId;
if (!resourceJid) {
if (id === LOCAL_LEVEL) {
id = APP.conference.localId;
if (!id) {
return;
}
}
if(resourceJid === largeVideoResourceJid) {
if(id === largeVideoId) {
window.requestAnimationFrame(function () {
AudioLevels.updateActiveSpeakerAudioLevel(audioLevel);
});
}
};
},
my.updateActiveSpeakerAudioLevel = function(audioLevel) {
if($("#activeSpeaker").css("visibility") == "hidden" || ASDrawContext === null)
updateActiveSpeakerAudioLevel (audioLevel) {
if($("#activeSpeaker").css("visibility") == "hidden" || ASDrawContext === null) {
return;
}
ASDrawContext.clearRect(0, 0, 300, 300);
if (!audioLevel)
if (!audioLevel) {
return;
}
ASDrawContext.shadowBlur = getShadowLevel(audioLevel);
// Fill the shape.
ASDrawContext.fill();
};
/**
* Resizes the given audio level canvas to match the given thumbnail size.
*/
function resizeAudioLevelCanvas(audioLevelCanvas,
thumbnailWidth,
thumbnailHeight) {
audioLevelCanvas.width = thumbnailWidth + interfaceConfig.CANVAS_EXTRA;
audioLevelCanvas.height = thumbnailHeight + interfaceConfig.CANVAS_EXTRA;
}
};
/**
* Draws the audio level canvas into the cached canvas object.
*
* @param resourceJid the resource jid indicating the video element for
* which we draw the audio level
* @param audioLevel the newAudio level to render
*/
function drawAudioLevelCanvas(resourceJid, audioLevel) {
if (!audioLevelCanvasCache[resourceJid]) {
var videoSpanId = getVideoSpanId(resourceJid);
var audioLevelCanvasOrig = $('#' + videoSpanId + '>canvas').get(0);
/*
* FIXME Testing has shown that audioLevelCanvasOrig may not exist.
* In such a case, the method CanvasUtil.cloneCanvas may throw an
* error. Since audio levels are frequently updated, the errors have
* been observed to pile into the console, strain the CPU.
*/
if (audioLevelCanvasOrig) {
audioLevelCanvasCache[resourceJid] =
CanvasUtil.cloneCanvas(audioLevelCanvasOrig);
}
}
var canvas = audioLevelCanvasCache[resourceJid];
if (!canvas)
return;
var drawContext = canvas.getContext('2d');
drawContext.clearRect(0, 0, canvas.width, canvas.height);
var shadowLevel = getShadowLevel(audioLevel);
if (shadowLevel > 0) {
// drawContext, x, y, w, h, r, shadowColor, shadowLevel
CanvasUtil.drawRoundRectGlow(drawContext,
interfaceConfig.CANVAS_EXTRA / 2, interfaceConfig.CANVAS_EXTRA / 2,
canvas.width - interfaceConfig.CANVAS_EXTRA,
canvas.height - interfaceConfig.CANVAS_EXTRA,
interfaceConfig.CANVAS_RADIUS,
interfaceConfig.SHADOW_COLOR,
shadowLevel);
}
}
/**
* Returns the shadow/glow level for the given audio level.
*
* @param audioLevel the audio level from which we determine the shadow
* level
*/
function getShadowLevel (audioLevel) {
var shadowLevel = 0;
if (audioLevel <= 0.3) {
shadowLevel = Math.round(interfaceConfig.CANVAS_EXTRA/2*(audioLevel/0.3));
}
else if (audioLevel <= 0.6) {
shadowLevel = Math.round(interfaceConfig.CANVAS_EXTRA/2*((audioLevel - 0.3) / 0.3));
}
else {
shadowLevel = Math.round(interfaceConfig.CANVAS_EXTRA/2*((audioLevel - 0.6) / 0.4));
}
return shadowLevel;
}
/**
* Returns the video span id corresponding to the given resourceJid or local
* user.
*/
function getVideoSpanId(resourceJid) {
var videoSpanId = null;
if (resourceJid === AudioLevels.LOCAL_LEVEL || APP.conference.isLocalId(resourceJid)) {
videoSpanId = 'localVideoContainer';
} else {
videoSpanId = 'participant_' + resourceJid;
}
return videoSpanId;
}
/**
* Indicates that the remote video has been resized.
*/
$(document).bind('remotevideo.resized', function (event, width, height) {
var resized = false;
$('#remoteVideos>span>canvas').each(function() {
var canvas = $(this).get(0);
if (canvas.width !== width + interfaceConfig.CANVAS_EXTRA) {
canvas.width = width + interfaceConfig.CANVAS_EXTRA;
resized = true;
}
if (canvas.height !== height + interfaceConfig.CANVAS_EXTRA) {
canvas.height = height + interfaceConfig.CANVAS_EXTRA;
resized = true;
}
});
if (resized)
Object.keys(audioLevelCanvasCache).forEach(function (resourceJid) {
audioLevelCanvasCache[resourceJid].width =
width + interfaceConfig.CANVAS_EXTRA;
audioLevelCanvasCache[resourceJid].height =
height + interfaceConfig.CANVAS_EXTRA;
});
});
return my;
})(AudioLevels || {});
module.exports = AudioLevels;
export default AudioLevels;

View File

@ -1,7 +1,7 @@
/**
* Utility class for drawing canvas shapes.
*/
var CanvasUtil = (function(my) {
const CanvasUtil = {
/**
* Draws a round rectangle with a glow. The glowWidth indicates the depth
@ -15,8 +15,7 @@ var CanvasUtil = (function(my) {
* @param glowColor the color of the glow
* @param glowWidth the width of the glow
*/
my.drawRoundRectGlow
= function(drawContext, x, y, w, h, r, glowColor, glowWidth) {
drawRoundRectGlow (drawContext, x, y, w, h, r, glowColor, glowWidth) {
// Save the previous state of the context.
drawContext.save();
@ -73,14 +72,14 @@ var CanvasUtil = (function(my) {
// Restore the previous context state.
drawContext.restore();
};
},
/**
* Clones the given canvas.
*
* @return the new cloned canvas.
*/
my.cloneCanvas = function (oldCanvas) {
cloneCanvas (oldCanvas) {
/*
* FIXME Testing has shown that oldCanvas may not exist. In such a case,
* the method CanvasUtil.cloneCanvas may throw an error. Since audio
@ -103,9 +102,7 @@ var CanvasUtil = (function(my) {
//return the new canvas
return newCanvas;
};
}
};
return my;
})(CanvasUtil || {});
module.exports = CanvasUtil;
export default CanvasUtil;

View File

@ -1,124 +0,0 @@
/* global $, APP*/
var LoginDialog = require('./LoginDialog');
var Moderator = require('../../xmpp/moderator');
/* Initial "authentication required" dialog */
var authDialog = null;
/* Loop retry ID that wits for other user to create the room */
var authRetryId = null;
var authenticationWindow = null;
var Authentication = {
openAuthenticationDialog: function (roomName, intervalCallback, callback) {
// This is the loop that will wait for the room to be created by
// someone else. 'auth_required.moderator' will bring us back here.
authRetryId = window.setTimeout(intervalCallback, 5000);
// Show prompt only if it's not open
if (authDialog !== null) {
return;
}
// extract room name from 'room@muc.server.net'
var room = roomName.substr(0, roomName.indexOf('@'));
var title
= APP.translation.generateTranslationHTML("dialog.WaitingForHost");
var msg
= APP.translation.generateTranslationHTML(
"dialog.WaitForHostMsg", {room: room});
var buttonTxt
= APP.translation.generateTranslationHTML("dialog.IamHost");
var buttons = [];
buttons.push({title: buttonTxt, value: "authNow"});
authDialog = APP.UI.messageHandler.openDialog(
title,
msg,
true,
buttons,
function (onSubmitEvent, submitValue) {
// Do not close the dialog yet
onSubmitEvent.preventDefault();
// Open login popup
if (submitValue === 'authNow') {
callback();
}
}
);
},
closeAuthenticationWindow: function () {
if (authenticationWindow) {
authenticationWindow.close();
authenticationWindow = null;
}
},
xmppAuthenticate: function () {
var loginDialog = LoginDialog.show(
function (connection, state) {
if (!state) {
// User cancelled
loginDialog.close();
return;
} else if (state == APP.xmpp.Status.CONNECTED) {
loginDialog.close();
Authentication.stopInterval();
Authentication.closeAuthenticationDialog();
// Close the connection as anonymous one will be used
// to create the conference. Session-id will authorize
// the request.
connection.disconnect();
var roomName = APP.UI.generateRoomName();
Moderator.allocateConferenceFocus(roomName, function () {
// If it's not "on the fly" authentication now join
// the conference room
if (!APP.xmpp.isMUCJoined()) {
APP.UI.checkForNicknameAndJoin();
}
});
}
}, true);
},
focusAuthenticationWindow: function () {
// If auth window exists just bring it to the front
if (authenticationWindow) {
authenticationWindow.focus();
return;
}
},
closeAuthenticationDialog: function () {
// Close authentication dialog if opened
if (authDialog) {
authDialog.close();
authDialog = null;
}
},
createAuthenticationWindow: function (callback, url) {
authenticationWindow = APP.UI.messageHandler.openCenteredPopup(
url, 910, 660,
// On closed
function () {
// Close authentication dialog if opened
Authentication.closeAuthenticationDialog();
callback();
authenticationWindow = null;
});
return authenticationWindow;
},
stopInterval: function () {
// Clear retry interval, so that we don't call 'doJoinAfterFocus' twice
if (authRetryId) {
window.clearTimeout(authRetryId);
authRetryId = null;
}
}
};
module.exports = Authentication;

View File

@ -1,75 +1,78 @@
/* global $, APP, config*/
var XMPP = require('../../xmpp/xmpp');
var Moderator = require('../../xmpp/moderator');
var messageHandler = require('../util/MessageHandler');
//FIXME: use LoginDialog to add retries to XMPP.connect method used when
// anonymous domain is not enabled
function getPasswordInputHtml() {
let placeholder = config.hosts.authdomain
? "user identity"
: "user@domain.net";
let passRequiredMsg = APP.translation.translateString(
"dialog.passwordRequired"
);
return `
<h2 data-i18n="dialog.passwordRequired">${passRequiredMsg}</h2>
<input name="username" type="text" placeholder=${placeholder} autofocus>
<input name="password" type="password"
data-i18n="[placeholder]dialog.userPassword"
placeholder="user password">
`;
}
/**
* Creates new <tt>Dialog</tt> instance.
* @param callback <tt>function(Strophe.Connection, Strophe.Status)</tt> called
* when we either fail to connect or succeed(check Strophe.Status).
* @param obtainSession <tt>true</tt> if we want to send ConferenceIQ to Jicofo
* in order to create session-id after the connection is established.
* @constructor
*/
function Dialog(callback, obtainSession) {
var self = this;
var stop = false;
var connection = APP.xmpp.createConnection();
var message = '<h2 data-i18n="dialog.passwordRequired">';
message += APP.translation.translateString("dialog.passwordRequired");
message += '</h2>' +
'<input name="username" type="text" ';
if (config.hosts.authdomain) {
message += 'placeholder="user identity" autofocus>';
} else {
message += 'placeholder="user@domain.net" autofocus>';
function toJid(id) {
if (id.indexOf("@") >= 0) {
return id;
}
message += '<input name="password" ' +
'type="password" data-i18n="[placeholder]dialog.userPassword"' +
' placeholder="user password">';
var okButton = APP.translation.generateTranslationHTML("dialog.Ok");
let jid = id.concat('@');
if (config.hosts.authdomain) {
jid += config.hosts.authdomain;
} else {
jid += config.hosts.domain;
}
var cancelButton = APP.translation.generateTranslationHTML("dialog.Cancel");
return jid;
}
var states = {
function cancelButton() {
return {
title: APP.translation.generateTranslationHTML("dialog.Cancel"),
value: false
};
}
function Dialog(successCallback, cancelCallback) {
let loginButtons = [{
title: APP.translation.generateTranslationHTML("dialog.Ok"),
value: true
}];
let finishedButtons = [{
title: APP.translation.translateString('dialog.retry'),
value: 'retry'
}];
// show "cancel" button only if cancelCallback provided
if (cancelCallback) {
loginButtons.push(cancelButton());
finishedButtons.push(cancelButton());
}
const states = {
login: {
html: message,
buttons: [
{ title: okButton, value: true},
{ title: cancelButton, value: false}
],
html: getPasswordInputHtml(),
buttons: loginButtons,
focus: ':input:first',
submit: function (e, v, m, f) {
e.preventDefault();
if (v) {
var jid = f.username;
var password = f.password;
let jid = f.username;
let password = f.password;
if (jid && password) {
stop = false;
if (jid.indexOf("@") < 0) {
jid = jid.concat('@');
if (config.hosts.authdomain) {
jid += config.hosts.authdomain;
} else {
jid += config.hosts.domain;
}
}
connection.reset();
connDialog.goToState('connecting');
connection.connect(jid, password, stateHandler);
successCallback(toJid(jid), password);
}
} else {
// User cancelled
stop = true;
callback();
cancelCallback();
}
}
},
@ -82,115 +85,23 @@ function Dialog(callback, obtainSession) {
finished: {
title: APP.translation.translateString('dialog.error'),
html: '<div id="errorMessage"></div>',
buttons: [
{
title: APP.translation.translateString('dialog.retry'),
value: 'retry'
},
{
title: APP.translation.translateString('dialog.Cancel'),
value: 'cancel'
},
],
buttons: finishedButtons,
defaultButton: 0,
submit: function (e, v, m, f) {
e.preventDefault();
if (v === 'retry')
if (v === 'retry') {
connDialog.goToState('login');
else
callback();
} else {
// User cancelled
cancelCallback();
}
}
}
};
var connDialog
= APP.UI.messageHandler.openDialogWithStates(states,
{ persistent: true, closeText: '' }, null);
var stateHandler = function (status, message) {
if (stop) {
return;
}
var translateKey = "connection." + XMPP.getStatusString(status);
var statusStr = APP.translation.translateString(translateKey);
// Display current state
var connectionStatus =
connDialog.getState('connecting').find('#connectionStatus');
connectionStatus.text(statusStr);
switch (status) {
case XMPP.Status.CONNECTED:
stop = true;
if (!obtainSession) {
callback(connection, status);
return;
}
// Obtaining session-id status
connectionStatus.text(
APP.translation.translateString(
'connection.FETCH_SESSION_ID'));
// Authenticate with Jicofo and obtain session-id
var roomName = APP.UI.generateRoomName();
// Jicofo will return new session-id when connected
// from authenticated domain
connection.sendIQ(
Moderator.createConferenceIq(roomName),
function (result) {
connectionStatus.text(
APP.translation.translateString(
'connection.GOT_SESSION_ID'));
stop = true;
// Parse session-id
Moderator.parseSessionId(result);
callback(connection, status);
},
function (error) {
console.error("Auth on the fly failed", error);
stop = true;
var errorMsg =
APP.translation.translateString(
'connection.GET_SESSION_ID_ERROR') +
$(error).find('>error').attr('code');
self.displayError(errorMsg);
connection.disconnect();
});
break;
case XMPP.Status.AUTHFAIL:
case XMPP.Status.CONNFAIL:
case XMPP.Status.DISCONNECTED:
stop = true;
callback(connection, status);
var errorMessage = statusStr;
if (message)
{
errorMessage += ': ' + message;
}
self.displayError(errorMessage);
break;
default:
break;
}
};
var connDialog = messageHandler.openDialogWithStates(
states, { persistent: true, closeText: '' }, null
);
/**
* Displays error message in 'finished' state which allows either to cancel
@ -211,30 +122,60 @@ function Dialog(callback, obtainSession) {
* Closes LoginDialog.
*/
this.close = function () {
stop = true;
connDialog.close();
};
}
var LoginDialog = {
const LoginDialog = {
/**
* Displays login prompt used to establish new XMPP connection. Given
* <tt>callback(Strophe.Connection, Strophe.Status)</tt> function will be
* called when we connect successfully(status === CONNECTED) or when we fail
* to do so. On connection failure program can call Dialog.close() method in
* order to cancel or do nothing to let the user retry.
* @param callback <tt>function(Strophe.Connection, Strophe.Status)</tt>
* called when we either fail to connect or succeed(check
* Strophe.Status).
* @param obtainSession <tt>true</tt> if we want to send ConferenceIQ to
* Jicofo in order to create session-id after the connection is
* established.
* @returns {Dialog}
*/
show: function (callback, obtainSession) {
return new Dialog(callback, obtainSession);
showAuthDialog: function (successCallback, cancelCallback) {
return new Dialog(successCallback, cancelCallback);
},
showExternalAuthDialog: function (url, callback) {
var dialog = messageHandler.openCenteredPopup(
url, 910, 660,
// On closed
callback
);
if (!dialog) {
messageHandler.openMessageDialog(null, "dialog.popupError");
}
return dialog;
},
showAuthRequiredDialog: function (roomName, onAuthNow) {
var title = APP.translation.generateTranslationHTML(
"dialog.WaitingForHost"
);
var msg = APP.translation.generateTranslationHTML(
"dialog.WaitForHostMsg", {room: roomName}
);
var buttonTxt = APP.translation.generateTranslationHTML(
"dialog.IamHost"
);
var buttons = [{title: buttonTxt, value: "authNow"}];
return APP.UI.messageHandler.openDialog(
title,
msg,
true,
buttons,
function (e, submitValue) {
// Do not close the dialog yet
e.preventDefault();
// Open login popup
if (submitValue === 'authNow') {
onAuthNow();
}
}
);
}
};
module.exports = LoginDialog;
export default LoginDialog;

View File

@ -1,6 +1,8 @@
var ToolbarToggler = require("../toolbars/ToolbarToggler");
var UIUtil = require("../util/UIUtil");
var VideoLayout = require("../videolayout/VideoLayout");
/* global $, APP */
/* jshint -W101 */
import UIUtil from "../util/UIUtil";
import VideoLayout from "../videolayout/VideoLayout";
var messageHandler = require("../util/MessageHandler");
var PreziPlayer = require("./PreziPlayer");

View File

@ -1,102 +1,101 @@
/* global require, $ */
var Chat = require("./chat/Chat");
var ContactList = require("./contactlist/ContactList");
var Settings = require("./../../settings/Settings");
var SettingsMenu = require("./settings/SettingsMenu");
var VideoLayout = require("../videolayout/VideoLayout");
var ToolbarToggler = require("../toolbars/ToolbarToggler");
var UIUtil = require("../util/UIUtil");
var LargeVideo = require("../videolayout/LargeVideo");
import Chat from "./chat/Chat";
import ContactList from "./contactlist/ContactList";
import Settings from "./../../settings/Settings";
import SettingsMenu from "./settings/SettingsMenu";
import VideoLayout from "../videolayout/VideoLayout";
import ToolbarToggler from "../toolbars/ToolbarToggler";
import UIUtil from "../util/UIUtil";
import LargeVideo from "../videolayout/LargeVideo";
const buttons = {
'#chatspace': '#chatBottomButton',
'#contactlist': '#contactListButton',
'#settingsmenu': '#toolbar_button_settings'
};
var currentlyOpen = null;
/**
* Toggles the windows in the side panel
* @param object the window that should be shown
* @param selector the selector for the element containing the panel
* @param onOpenComplete function to be called when the panel is opened
* @param onOpen function to be called if the window is going to be opened
* @param onClose function to be called if the window is going to be closed
*/
function toggle (object, selector, onOpenComplete, onOpen, onClose) {
UIUtil.buttonClick(buttons[selector], "active");
if (object.isVisible()) {
$("#toast-container").animate({
right: 5
}, {
queue: false,
duration: 500
});
$(selector).hide("slide", {
direction: "right",
queue: false,
duration: 500
});
if(typeof onClose === "function") {
onClose();
}
currentlyOpen = null;
} else {
// Undock the toolbar when the chat is shown and if we're in a
// video mode.
if (LargeVideo.isLargeVideoVisible()) {
ToolbarToggler.dockToolbar(false);
}
if (currentlyOpen) {
var current = $(currentlyOpen);
UIUtil.buttonClick(buttons[currentlyOpen], "active");
current.css('z-index', 4);
setTimeout(function () {
current.css('display', 'none');
current.css('z-index', 5);
}, 500);
}
$("#toast-container").animate({
right: (PanelToggler.getPanelSize()[0] + 5)
}, {
queue: false,
duration: 500
});
$(selector).show("slide", {
direction: "right",
queue: false,
duration: 500,
complete: onOpenComplete
});
if(typeof onOpen === "function") {
onOpen();
}
currentlyOpen = selector;
}
}
/**
* Toggler for the chat, contact list, settings menu, etc..
*/
var PanelToggler = (function(my) {
var currentlyOpen = null;
var buttons = {
'#chatspace': '#chatBottomButton',
'#contactlist': '#contactListButton',
'#settingsmenu': '#toolbar_button_settings'
};
/**
* Toggles the windows in the side panel
* @param object the window that should be shown
* @param selector the selector for the element containing the panel
* @param onOpenComplete function to be called when the panel is opened
* @param onOpen function to be called if the window is going to be opened
* @param onClose function to be called if the window is going to be closed
*/
var toggle = function(object, selector, onOpenComplete, onOpen, onClose) {
UIUtil.buttonClick(buttons[selector], "active");
if (object.isVisible()) {
$("#toast-container").animate({
right: '5px'
},
{
queue: false,
duration: 500
});
$(selector).hide("slide", {
direction: "right",
queue: false,
duration: 500
});
if(typeof onClose === "function") {
onClose();
}
currentlyOpen = null;
}
else {
// Undock the toolbar when the chat is shown and if we're in a
// video mode.
if (LargeVideo.isLargeVideoVisible()) {
ToolbarToggler.dockToolbar(false);
}
if(currentlyOpen) {
var current = $(currentlyOpen);
UIUtil.buttonClick(buttons[currentlyOpen], "active");
current.css('z-index', 4);
setTimeout(function () {
current.css('display', 'none');
current.css('z-index', 5);
}, 500);
}
$("#toast-container").animate({
right: (PanelToggler.getPanelSize()[0] + 5) + 'px'
},
{
queue: false,
duration: 500
});
$(selector).show("slide", {
direction: "right",
queue: false,
duration: 500,
complete: onOpenComplete
});
if(typeof onOpen === "function") {
onOpen();
}
currentlyOpen = selector;
}
};
var PanelToggler = {
/**
* Opens / closes the chat area.
*/
my.toggleChat = function() {
var chatCompleteFunction = Chat.isVisible() ?
function() {} : function () {
Chat.scrollChatToBottom();
$('#chatspace').trigger('shown');
};
toggleChat () {
var chatCompleteFunction = Chat.isVisible()
? function () {}
: function () {
Chat.scrollChatToBottom();
$('#chatspace').trigger('shown');
};
VideoLayout.resizeVideoArea(!Chat.isVisible(), chatCompleteFunction);
@ -112,16 +111,24 @@ var PanelToggler = (function(my) {
}
},
null,
Chat.resizeChat,
() => this.resizeChat(),
null);
};
},
resizeChat () {
let [width, height] = this.getPanelSize();
Chat.resizeChat(width, height);
},
/**
* Opens / closes the contact list area.
*/
my.toggleContactList = function () {
var completeFunction = ContactList.isVisible() ?
function() {} : function () { $('#contactlist').trigger('shown');};
toggleContactList () {
var completeFunction = ContactList.isVisible()
? function () {}
: function () {
$('#contactlist').trigger('shown');
};
VideoLayout.resizeVideoArea(!ContactList.isVisible(), completeFunction);
toggle(ContactList,
@ -131,12 +138,12 @@ var PanelToggler = (function(my) {
ContactList.setVisualNotification(false);
},
null);
};
},
/**
* Opens / closes the settings menu
*/
my.toggleSettingsMenu = function() {
toggleSettingsMenu () {
VideoLayout.resizeVideoArea(!SettingsMenu.isVisible(), function (){});
toggle(SettingsMenu,
'#settingsmenu',
@ -147,12 +154,12 @@ var PanelToggler = (function(my) {
$('#setEmail').get(0).value = settings.email;
},
null);
};
},
/**
* Returns the size of the side panel.
*/
my.getPanelSize = function () {
getPanelSize () {
var availableHeight = window.innerHeight;
var availableWidth = window.innerWidth;
@ -162,16 +169,13 @@ var PanelToggler = (function(my) {
}
return [panelWidth, availableHeight];
};
},
my.isVisible = function() {
isVisible () {
return (Chat.isVisible() ||
ContactList.isVisible() ||
SettingsMenu.isVisible());
};
}
};
return my;
}(PanelToggler || {}));
module.exports = PanelToggler;
export default PanelToggler;

View File

@ -1,10 +1,13 @@
/* global APP, $ */
var Replacement = require("./Replacement");
var CommandsProcessor = require("./Commands");
var ToolbarToggler = require("../../toolbars/ToolbarToggler");
import {processReplacements, linkify} from './Replacement';
import CommandsProcessor from './Commands';
import ToolbarToggler from '../../toolbars/ToolbarToggler';
import UIUtil from '../../util/UIUtil';
import UIEvents from '../../../../service/UI/UIEvents';
var smileys = require("./smileys.json").smileys;
var UIUtil = require("../../util/UIUtil");
var UIEvents = require("../../../../service/UI/UIEvents");
var notificationInterval = false;
var unreadMessages = 0;
@ -164,11 +167,11 @@ function resizeChatConversation() {
/**
* Chat related user interface.
*/
var Chat = (function (my) {
var Chat = {
/**
* Initializes chat related interface.
*/
my.init = function (eventEmitter) {
init (eventEmitter) {
if (APP.settings.getDisplayName()) {
Chat.setChatConversationMode(true);
}
@ -178,10 +181,7 @@ var Chat = (function (my) {
event.preventDefault();
var val = UIUtil.escapeHtml(this.value);
this.value = '';
if (APP.settings.getDisplayName()) {
eventEmitter.emit(UIEvents.NICKNAME_CHANGED, val);
return;
}
eventEmitter.emit(UIEvents.NICKNAME_CHANGED, val);
}
});
@ -192,11 +192,10 @@ var Chat = (function (my) {
var value = this.value;
usermsg.val('').trigger('autosize.resize');
this.focus();
var command = new CommandsProcessor(value);
if(command.isCommand()) {
var command = new CommandsProcessor(value, eventEmitter);
if (command.isCommand()) {
command.processCommand();
}
else {
} else {
var message = UIUtil.escapeHtml(value);
eventEmitter.emit(UIEvents.MESSAGE_CREATED, message);
}
@ -216,19 +215,17 @@ var Chat = (function (my) {
});
addSmileys();
};
},
/**
* Appends the given message to the chat conversation.
*/
my.updateChatConversation =
function (from, displayName, message, stamp) {
updateChatConversation (id, displayName, message, stamp) {
var divClassName = '';
if (APP.xmpp.myJid() === from) {
if (APP.conference.isLocalId(id)) {
divClassName = "localuser";
}
else {
} else {
divClassName = "remoteuser";
if (!Chat.isVisible()) {
@ -244,7 +241,7 @@ var Chat = (function (my) {
var escMessage = message.replace(/</g, '&lt;').
replace(/>/g, '&gt;').replace(/\n/g, '<br/>');
var escDisplayName = UIUtil.escapeHtml(displayName);
message = Replacement.processReplacements(escMessage);
message = processReplacements(escMessage);
var messageContainer =
'<div class="chatmessage">'+
@ -257,14 +254,14 @@ var Chat = (function (my) {
$('#chatconversation').append(messageContainer);
$('#chatconversation').animate(
{ scrollTop: $('#chatconversation')[0].scrollHeight}, 1000);
};
},
/**
* Appends error message to the conversation
* @param errorMessage the received error message.
* @param originalText the original message.
*/
my.chatAddError = function(errorMessage, originalText) {
chatAddError (errorMessage, originalText) {
errorMessage = UIUtil.escapeHtml(errorMessage);
originalText = UIUtil.escapeHtml(originalText);
@ -275,28 +272,28 @@ var Chat = (function (my) {
(errorMessage? (' Reason: ' + errorMessage) : '') + '</div>');
$('#chatconversation').animate(
{ scrollTop: $('#chatconversation')[0].scrollHeight}, 1000);
};
},
/**
* Sets the subject to the UI
* @param subject the subject
*/
my.chatSetSubject = function(subject) {
if (subject)
setSubject (subject) {
if (subject) {
subject = subject.trim();
$('#subject').html(Replacement.linkify(UIUtil.escapeHtml(subject)));
if(subject === "") {
}
$('#subject').html(linkify(UIUtil.escapeHtml(subject)));
if (subject) {
$("#subject").css({display: "block"});
} else {
$("#subject").css({display: "none"});
}
else {
$("#subject").css({display: "block"});
}
};
},
/**
* Sets the chat conversation mode.
*/
my.setChatConversationMode = function (isConversationMode) {
setChatConversationMode (isConversationMode) {
if (isConversationMode) {
$('#nickname').css({visibility: 'hidden'});
$('#chatconversation').css({visibility: 'visible'});
@ -304,42 +301,37 @@ var Chat = (function (my) {
$('#smileysarea').css({visibility: 'visible'});
$('#usermsg').focus();
}
};
},
/**
* Resizes the chat area.
*/
my.resizeChat = function () {
var chatSize = require("../SidePanelToggler").getPanelSize();
$('#chatspace').width(chatSize[0]);
$('#chatspace').height(chatSize[1]);
resizeChat (width, height) {
$('#chatspace').width(width).height(height);
resizeChatConversation();
};
},
/**
* Indicates if the chat is currently visible.
*/
my.isVisible = function () {
isVisible () {
return $('#chatspace').is(":visible");
};
},
/**
* Shows and hides the window with the smileys
*/
my.toggleSmileys = toggleSmileys;
toggleSmileys,
/**
* Scrolls chat to the bottom.
*/
my.scrollChatToBottom = function() {
scrollChatToBottom () {
setTimeout(function () {
$('#chatconversation').scrollTop(
$('#chatconversation')[0].scrollHeight);
}, 5);
};
}
};
return my;
}(Chat || {}));
module.exports = Chat;
export default Chat;

View File

@ -1,12 +1,13 @@
/* global APP, require */
var UIUtil = require("../../util/UIUtil");
/* global APP */
import UIUtil from '../../util/UIUtil';
import UIEvents from '../../../../service/UI/UIEvents';
/**
* List with supported commands. The keys are the names of the commands and
* the value is the function that processes the message.
* @type {{String: function}}
*/
var commands = {
const commands = {
"topic" : processTopic
};
@ -29,9 +30,9 @@ function getCommand(message) {
* Processes the data for topic command.
* @param commandArguments the arguments of the topic command.
*/
function processTopic(commandArguments) {
function processTopic(commandArguments, emitter) {
var topic = UIUtil.escapeHtml(commandArguments);
APP.xmpp.setSubject(topic);
emitter.emit(UIEvents.TOPIC_CHANGED, topic);
}
/**
@ -40,9 +41,11 @@ function processTopic(commandArguments) {
* @param message the message
* @constructor
*/
function CommandsProcessor(message) {
function CommandsProcessor(message, emitter) {
var command = getCommand(message);
this.emitter = emitter;
/**
* Returns the name of the command.
* @returns {String} the command
@ -80,7 +83,7 @@ CommandsProcessor.prototype.processCommand = function() {
if(!this.isCommand())
return;
commands[this.getCommand()](this.getArgument());
commands[this.getCommand()](this.getArgument(), this.emitter);
};
module.exports = CommandsProcessor;
export default CommandsProcessor;

View File

@ -1,10 +1,10 @@
/* jshint -W101 */
var Smileys = require("./smileys.json");
/**
* Processes links and smileys in "body"
*/
function processReplacements(body)
{
export function processReplacements(body) {
//make links clickable
body = linkify(body);
@ -18,8 +18,7 @@ function processReplacements(body)
* Finds and replaces all links in the links in "body"
* with their <a href=""></a>
*/
function linkify(inputText)
{
export function linkify(inputText) {
var replacedText, replacePattern1, replacePattern2, replacePattern3;
//URLs starting with http://, https://, or ftp://
@ -40,8 +39,7 @@ function linkify(inputText)
/**
* Replaces common smiley strings with images
*/
function smilify(body)
{
function smilify(body) {
if(!body) {
return body;
}
@ -56,8 +54,3 @@ function smilify(body)
return body;
}
module.exports = {
processReplacements: processReplacements,
linkify: linkify
};

View File

@ -1,8 +1,9 @@
/* global $, APP, Strophe */
var Avatar = require('../../avatar/Avatar');
/* global $, APP */
import Avatar from '../../avatar/Avatar';
import UIEvents from '../../../../service/UI/UIEvents';
var numberOfContacts = 0;
var notificationInterval;
let numberOfContacts = 0;
let notificationInterval;
/**
* Updates the number of participants in the contact list button and sets
@ -30,7 +31,7 @@ function updateNumberOfParticipants(delta) {
* @return {object} the newly created avatar element
*/
function createAvatar(jid) {
var avatar = document.createElement('img');
let avatar = document.createElement('img');
avatar.className = "icon-avatar avatar";
avatar.src = Avatar.getContactListUrl(jid);
@ -43,10 +44,10 @@ function createAvatar(jid) {
* @param displayName the display name to set
*/
function createDisplayNameParagraph(key, displayName) {
var p = document.createElement('p');
if(displayName)
let p = document.createElement('p');
if (displayName) {
p.innerText = displayName;
else if(key) {
} else if(key) {
p.setAttribute("data-i18n",key);
p.innerText = APP.translation.translateString(key);
}
@ -64,93 +65,79 @@ function stopGlowing(glower) {
}
}
function getContactEl (id) {
return $(`#contacts>li[id="${id}"]`);
}
function contactElExists (id) {
return getContactEl(id).length > 0;
}
/**
* Contact list.
*/
var ContactList = {
init (emitter) {
this.emitter = emitter;
},
/**
* Indicates if the chat is currently visible.
*
* @return <tt>true</tt> if the chat is currently visible, <tt>false</tt> -
* otherwise
*/
isVisible: function () {
isVisible () {
return $('#contactlist').is(":visible");
},
/**
* Adds a contact for the given peerJid if such doesn't yet exist.
* Adds a contact for the given id.
*
* @param peerJid the peerJid corresponding to the contact
*/
ensureAddContact: function (peerJid) {
var resourceJid = Strophe.getResourceFromJid(peerJid);
addContact (id) {
let contactlist = $('#contacts');
var contact = $('#contacts>li[id="' + resourceJid + '"]');
if (!contact || contact.length <= 0)
ContactList.addContact(peerJid);
},
/**
* Adds a contact for the given peer jid.
*
* @param peerJid the jid of the contact to add
*/
addContact: function (peerJid) {
var resourceJid = Strophe.getResourceFromJid(peerJid);
var contactlist = $('#contacts');
var newContact = document.createElement('li');
newContact.id = resourceJid;
let newContact = document.createElement('li');
newContact.id = id;
newContact.className = "clickable";
newContact.onclick = function (event) {
newContact.onclick = (event) => {
if (event.currentTarget.className === "clickable") {
$(ContactList).trigger('contactclicked', [peerJid]);
this.emitter.emit(UIEvents.CONTACT_CLICKED, id);
}
};
newContact.appendChild(createAvatar(peerJid));
newContact.appendChild(createAvatar(id));
newContact.appendChild(createDisplayNameParagraph("participant"));
if (resourceJid === APP.xmpp.myResource()) {
if (APP.conference.isLocalId(id)) {
contactlist.prepend(newContact);
}
else {
} else {
contactlist.append(newContact);
}
updateNumberOfParticipants(1);
},
/**
* Removes a contact for the given peer jid.
* Removes a contact for the given id.
*
* @param peerJid the peerJid corresponding to the contact to remove
*/
removeContact: function (peerJid) {
var resourceJid = Strophe.getResourceFromJid(peerJid);
var contact = $('#contacts>li[id="' + resourceJid + '"]');
if (contact && contact.length > 0) {
var contactlist = $('#contactlist>ul');
contactlist.get(0).removeChild(contact.get(0));
removeContact (id) {
let contact = getContactEl(id);
if (contact.length > 0) {
contact.remove();
updateNumberOfParticipants(-1);
}
},
setVisualNotification: function (show, stopGlowingIn) {
var glower = $('#contactListButton');
setVisualNotification (show, stopGlowingIn) {
let glower = $('#contactListButton');
if (show && !notificationInterval) {
notificationInterval = window.setInterval(function () {
glower.toggleClass('active glowing');
}, 800);
}
else if (!show && notificationInterval) {
} else if (!show && notificationInterval) {
stopGlowing(glower);
}
if (stopGlowingIn) {
@ -160,34 +147,28 @@ var ContactList = {
}
},
setClickable: function (resourceJid, isClickable) {
var contact = $('#contacts>li[id="' + resourceJid + '"]');
if (isClickable) {
contact.addClass('clickable');
} else {
contact.removeClass('clickable');
}
setClickable (id, isClickable) {
getContactEl(id).toggleClass('clickable', isClickable);
},
onDisplayNameChange: function (id, displayName) {
onDisplayNameChange (id, displayName) {
if (id === 'localVideoContainer') {
id = APP.conference.localId;
}
var contactName = $('#contacts #' + id + '>p');
let contactName = $(`#contacts #${id}>p`);
if (contactName && displayName && displayName.length > 0) {
if (displayName) {
contactName.html(displayName);
}
},
changeUserAvatar: function (id, contactListUrl) {
changeUserAvatar (id, contactListUrl) {
// set the avatar in the contact list
var contact = $('#' + id + '>img');
if (contact && contact.length > 0) {
contact.get(0).src = contactListUrl;
let contact = $(`#${id}>img`);
if (contact.length > 0) {
contact.attr('src', contactListUrl);
}
}
};
module.exports = ContactList;
export default ContactList;

View File

@ -1,12 +1,11 @@
/* global APP, $ */
var Settings = require("./../../../settings/Settings");
var UIUtil = require("../../util/UIUtil");
var languages = require("../../../../service/translation/languages");
var UIEvents = require("../../../../service/UI/UIEvents");
import UIUtil from "../../util/UIUtil";
import UIEvents from "../../../../service/UI/UIEvents";
import languages from "../../../../service/translation/languages";
function generateLanguagesSelectBox() {
var currentLang = APP.translation.getCurrentLanguage();
var html = "<select id=\"languages_selectbox\">";
var html = '<select id="languages_selectbox">';
var langArray = languages.getLanguages();
for(var i = 0; i < langArray.length; i++) {
var lang = langArray[i];
@ -22,7 +21,7 @@ function generateLanguagesSelectBox() {
}
var SettingsMenu = {
const SettingsMenu = {
init: function (emitter) {
this.emitter = emitter;
@ -98,5 +97,4 @@ var SettingsMenu = {
}
};
module.exports = SettingsMenu;
export default SettingsMenu;

View File

@ -1,66 +1,48 @@
/* global $ */
var PanelToggler = require("../side_pannels/SidePanelToggler");
var UIUtil = require("../util/UIUtil");
var AnalyticsAdapter = require("../../statistics/AnalyticsAdapter");
var UIEvents = require("../../../service/UI/UIEvents");
import UIUtil from '../util/UIUtil';
import UIEvents from '../../../service/UI/UIEvents';
import AnalyticsAdapter from '../../statistics/AnalyticsAdapter';
var eventEmitter = null;
var buttonHandlers = {
"bottom_toolbar_contact_list": function () {
AnalyticsAdapter.sendEvent('bottomtoolbar.contacts.toggled');
BottomToolbar.toggleContactList();
},
"bottom_toolbar_film_strip": function () {
AnalyticsAdapter.sendEvent('bottomtoolbar.filmstrip.toggled');
BottomToolbar.toggleFilmStrip();
},
"bottom_toolbar_chat": function () {
AnalyticsAdapter.sendEvent('bottomtoolbar.chat.toggled');
BottomToolbar.toggleChat();
}
};
var defaultBottomToolbarButtons = {
'chat': '#bottom_toolbar_chat',
'contacts': '#bottom_toolbar_contact_list',
const defaultBottomToolbarButtons = {
'chat': '#bottom_toolbar_chat',
'contacts': '#bottom_toolbar_contact_list',
'filmstrip': '#bottom_toolbar_film_strip'
};
$(document).bind("remotevideo.resized", function (event, width, height) {
let toolbar = $('#bottomToolbar');
let bottom = (height - toolbar.outerHeight())/2 + 18;
var BottomToolbar = (function (my) {
my.init = function (emitter) {
eventEmitter = emitter;
toolbar.css({bottom});
});
const BottomToolbar = {
init (emitter) {
UIUtil.hideDisabledButtons(defaultBottomToolbarButtons);
for(var k in buttonHandlers)
$("#" + k).click(buttonHandlers[k]);
};
const buttonHandlers = {
"bottom_toolbar_contact_list": function () {
AnalyticsAdapter.sendEvent('bottomtoolbar.contacts.toggled');
emitter.emit(UIEvents.TOGGLE_CONTACT_LIST);
},
"bottom_toolbar_film_strip": function () {
AnalyticsAdapter.sendEvent('bottomtoolbar.filmstrip.toggled');
emitter.emit(UIEvents.TOGGLE_FILM_STRIP);
},
"bottom_toolbar_chat": function () {
AnalyticsAdapter.sendEvent('bottomtoolbar.chat.toggled');
emitter.emit(UIEvents.TOGGLE_CHAT);
}
};
my.toggleChat = function() {
PanelToggler.toggleChat();
};
Object.keys(buttonHandlers).forEach(
buttonId => $(`#${buttonId}`).click(buttonHandlers[buttonId])
);
},
my.toggleContactList = function() {
PanelToggler.toggleContactList();
};
toggleFilmStrip () {
$("#remoteVideos").toggleClass("hidden");
}
};
my.toggleFilmStrip = function() {
var filmstrip = $("#remoteVideos");
filmstrip.toggleClass("hidden");
eventEmitter.emit( UIEvents.FILM_STRIP_TOGGLED,
filmstrip.hasClass("hidden"));
};
$(document).bind("remotevideo.resized", function (event, width, height) {
var bottom = (height - $('#bottomToolbar').outerHeight())/2 + 18;
$('#bottomToolbar').css({bottom: bottom + 'px'});
});
return my;
}(BottomToolbar || {}));
module.exports = BottomToolbar;
export default BottomToolbar;

View File

@ -1,26 +1,96 @@
/* global APP, $, config, interfaceConfig */
/* jshint -W101 */
var messageHandler = require("../util/MessageHandler");
var BottomToolbar = require("./BottomToolbar");
var Prezi = require("../prezi/Prezi");
var Etherpad = require("../etherpad/Etherpad");
var PanelToggler = require("../side_pannels/SidePanelToggler");
var Authentication = require("../authentication/Authentication");
var UIUtil = require("../util/UIUtil");
var AuthenticationEvents
= require("../../../service/authentication/AuthenticationEvents");
var AnalyticsAdapter = require("../../statistics/AnalyticsAdapter");
var Feedback = require("../Feedback");
var UIEvents = require("../../../service/UI/UIEvents");
import messageHandler from '../util/MessageHandler';
import UIUtil from '../util/UIUtil';
import AnalyticsAdapter from '../../statistics/AnalyticsAdapter';
import UIEvents from '../../../service/UI/UIEvents';
var roomUrl = null;
var sharedKey = '';
var recordingToaster = null;
var emitter = null;
let roomUrl = null;
let recordingToaster = null;
let emitter = null;
var buttonHandlers = {
/**
* Opens the invite link dialog.
*/
function openLinkDialog () {
let inviteAttributes;
if (roomUrl === null) {
inviteAttributes = 'data-i18n="[value]roomUrlDefaultMsg" value="' +
APP.translation.translateString("roomUrlDefaultMsg") + '"';
} else {
inviteAttributes = "value=\"" + encodeURI(roomUrl) + "\"";
}
messageHandler.openTwoButtonDialog(
"dialog.shareLink", null, null,
`<input id="inviteLinkRef" type="text" ${inviteAttributes} onclick="this.select();" readonly>`,
false, "dialog.Invite",
function (e, v) {
if (v && roomUrl) {
emitter.emit(UIEvents.USER_INVITED, roomUrl);
}
},
function (event) {
if (roomUrl) {
document.getElementById('inviteLinkRef').select();
} else {
if (event && event.target) {
$(event.target).find('button[value=true]').prop('disabled', true);
}
}
}
);
}
// Sets the state of the recording button
function setRecordingButtonState (recordingState) {
let selector = $('#toolbar_button_record');
if (recordingState === 'on') {
selector.removeClass("icon-recEnable");
selector.addClass("icon-recEnable active");
$("#largeVideo").toggleClass("videoMessageFilter", true);
let recordOnKey = "recording.on";
$('#videoConnectionMessage').attr("data-i18n", recordOnKey);
$('#videoConnectionMessage').text(APP.translation.translateString(recordOnKey));
setTimeout(function(){
$("#largeVideo").toggleClass("videoMessageFilter", false);
$('#videoConnectionMessage').css({display: "none"});
}, 1500);
recordingToaster = messageHandler.notify(
null, "recording.toaster", null,
null, null,
{timeOut: 0, closeButton: null, tapToDismiss: false}
);
} else if (recordingState === 'off') {
selector.removeClass("icon-recEnable active");
selector.addClass("icon-recEnable");
$("#largeVideo").toggleClass("videoMessageFilter", false);
$('#videoConnectionMessage').css({display: "none"});
if (recordingToaster) {
messageHandler.remove(recordingToaster);
}
} else if (recordingState === 'pending') {
selector.removeClass("icon-recEnable active");
selector.addClass("icon-recEnable");
$("#largeVideo").toggleClass("videoMessageFilter", true);
let recordPendingKey = "recording.pending";
$('#videoConnectionMessage').attr("data-i18n", recordPendingKey);
$('#videoConnectionMessage').text(APP.translation.translateString(recordPendingKey));
$('#videoConnectionMessage').css({display: "block"});
}
}
const buttonHandlers = {
"toolbar_button_mute": function () {
if (APP.RTC.localAudio.isMuted()) {
if (APP.conference.audioMuted) {
AnalyticsAdapter.sendEvent('toolbar.audio.unmuted');
emitter.emit(UIEvents.AUDIO_MUTED, false);
} else {
@ -29,7 +99,7 @@ var buttonHandlers = {
}
},
"toolbar_button_camera": function () {
if (APP.RTC.localVideo.isMuted()) {
if (APP.conference.videoMuted) {
AnalyticsAdapter.sendEvent('toolbar.video.enabled');
emitter.emit(UIEvents.VIDEO_MUTED, false);
} else {
@ -37,36 +107,28 @@ var buttonHandlers = {
emitter.emit(UIEvents.VIDEO_MUTED, true);
}
},
/*"toolbar_button_authentication": function () {
return Toolbar.authenticateClicked();
},*/
"toolbar_button_record": function () {
AnalyticsAdapter.sendEvent('toolbar.recording.toggled');
return toggleRecording();
emitter.emit(UIEvents.RECORDING_TOGGLE);
},
"toolbar_button_security": function () {
if (sharedKey) {
AnalyticsAdapter.sendEvent('toolbar.lock.disabled');
} else {
AnalyticsAdapter.sendEvent('toolbar.lock.enabled');
}
return Toolbar.openLockDialog();
emitter.emit(UIEvents.ROOM_LOCK_CLICKED);
},
"toolbar_button_link": function () {
AnalyticsAdapter.sendEvent('toolbar.invite.clicked');
return Toolbar.openLinkDialog();
openLinkDialog();
},
"toolbar_button_chat": function () {
AnalyticsAdapter.sendEvent('toolbar.chat.toggled');
return BottomToolbar.toggleChat();
emitter.emit(UIEvents.TOGGLE_CHAT);
},
"toolbar_button_prezi": function () {
AnalyticsAdapter.sendEvent('toolbar.prezi.clicked');
return Prezi.openPreziDialog();
emitter.emit(UIEvents.PREZI_CLICKED);
},
"toolbar_button_etherpad": function () {
AnalyticsAdapter.sendEvent('toolbar.etherpad.clicked');
return Etherpad.toggleEtherpad(0);
emitter.emit(UIEvents.ETHERPAD_CLICKED);
},
"toolbar_button_desktopsharing": function () {
if (APP.desktopsharing.isUsingScreenStream) {
@ -74,32 +136,32 @@ var buttonHandlers = {
} else {
AnalyticsAdapter.sendEvent('toolbar.screen.enabled');
}
return APP.desktopsharing.toggleScreenSharing();
APP.desktopsharing.toggleScreenSharing();
},
"toolbar_button_fullScreen": function() {
AnalyticsAdapter.sendEvent('toolbar.fullscreen.enabled');
UIUtil.buttonClick("#toolbar_button_fullScreen", "icon-full-screen icon-exit-full-screen");
return Toolbar.toggleFullScreen();
emitter.emit(UIEvents.FULLSCREEN_TOGGLE);
},
"toolbar_button_sip": function () {
AnalyticsAdapter.sendEvent('toolbar.sip.clicked');
return callSipButtonClicked();
showSipNumberInput();
},
"toolbar_button_dialpad": function () {
AnalyticsAdapter.sendEvent('toolbar.sip.dialpad.clicked');
return dialpadButtonClicked();
dialpadButtonClicked();
},
"toolbar_button_settings": function () {
AnalyticsAdapter.sendEvent('toolbar.settings.toggled');
PanelToggler.toggleSettingsMenu();
emitter.emit(UIEvents.TOGGLE_SETTINGS);
},
"toolbar_button_hangup": function () {
AnalyticsAdapter.sendEvent('toolbar.hangup');
return hangup();
emitter.emit(UIEvents.HANGUP);
},
"toolbar_button_login": function () {
AnalyticsAdapter.sendEvent('toolbar.authenticate.login.clicked');
Toolbar.authenticateClicked();
emitter.emit(UIEvents.AUTH_CLICKED);
},
"toolbar_button_logout": function () {
AnalyticsAdapter.sendEvent('toolbar.authenticate.logout.clicked');
@ -113,631 +175,213 @@ var buttonHandlers = {
"dialog.Yes",
function (evt, yes) {
if (yes) {
APP.xmpp.logout(function (url) {
if (url) {
window.location.href = url;
} else {
hangup();
}
});
emitter.emit(UIEvents.LOGOUT);
}
});
}
};
var defaultToolbarButtons = {
'microphone': '#toolbar_button_mute',
'camera': '#toolbar_button_camera',
'desktop': '#toolbar_button_desktopsharing',
'security': '#toolbar_button_security',
'invite': '#toolbar_button_link',
'chat': '#toolbar_button_chat',
'prezi': '#toolbar_button_prezi',
'etherpad': '#toolbar_button_etherpad',
'fullscreen': '#toolbar_button_fullScreen',
'settings': '#toolbar_button_settings',
'hangup': '#toolbar_button_hangup'
};
/**
* Hangs up this call.
*/
function hangup() {
var conferenceDispose = function () {
APP.xmpp.disposeConference();
if (config.enableWelcomePage) {
setTimeout(function() {
window.localStorage.welcomePageDisabled = false;
window.location.pathname = "/";
}, 3000);
}
};
if (Feedback.isEnabled())
{
// If the user has already entered feedback, we'll show the window and
// immidiately start the conference dispose timeout.
if (Feedback.feedbackScore > 0) {
Feedback.openFeedbackWindow();
conferenceDispose();
}
// Otherwise we'll wait for user's feedback.
else
Feedback.openFeedbackWindow(conferenceDispose);
}
else {
conferenceDispose();
// If the feedback functionality isn't enabled we show a thank you
// dialog.
APP.UI.messageHandler.openMessageDialog(null, null, null,
APP.translation.translateString("dialog.thankYou",
{appName:interfaceConfig.APP_NAME}));
}
}
/**
* Starts or stops the recording for the conference.
*/
function toggleRecording(predefinedToken) {
APP.xmpp.toggleRecording(function (callback) {
if (predefinedToken) {
callback(UIUtil.escapeHtml(predefinedToken));
return;
}
var msg = APP.translation.generateTranslationHTML(
"dialog.recordingToken");
var token = APP.translation.translateString("dialog.token");
APP.UI.messageHandler.openTwoButtonDialog(null, null, null,
'<h2>' + msg + '</h2>' +
'<input name="recordingToken" type="text" ' +
' data-i18n="[placeholder]dialog.token" ' +
'placeholder="' + token + '" autofocus>',
false,
"dialog.Save",
function (e, v, m, f) {
if (v) {
var token = f.recordingToken;
if (token) {
callback(UIUtil.escapeHtml(token));
}
}
},
null,
function () { },
':input:first'
}
);
}, Toolbar.setRecordingButtonState);
}
/**
* Locks / unlocks the room.
*/
function lockRoom(lock) {
var currentSharedKey = '';
if (lock)
currentSharedKey = sharedKey;
APP.xmpp.lockRoom(currentSharedKey, function (res) {
// password is required
if (sharedKey) {
console.log('set room password');
Toolbar.lockLockButton();
}
else {
console.log('removed room password');
Toolbar.unlockLockButton();
}
}, function (err) {
console.warn('setting password failed', err);
messageHandler.showError("dialog.lockTitle",
"dialog.lockMessage");
Toolbar.setSharedKey('');
}, function () {
console.warn('room passwords not supported');
messageHandler.showError("dialog.warning",
"dialog.passwordNotSupported");
Toolbar.setSharedKey('');
});
}
/**
* Invite participants to conference.
*/
function inviteParticipants() {
if (roomUrl === null)
return;
var sharedKeyText = "";
if (sharedKey && sharedKey.length > 0) {
sharedKeyText =
APP.translation.translateString("email.sharedKey",
{sharedKey: sharedKey});
sharedKeyText = sharedKeyText.replace(/\n/g, "%0D%0A");
}
var supportedBrowsers = "Chromium, Google Chrome " +
APP.translation.translateString("email.and") + " Opera";
var conferenceName = roomUrl.substring(roomUrl.lastIndexOf('/') + 1);
var subject = APP.translation.translateString("email.subject",
{appName:interfaceConfig.APP_NAME, conferenceName: conferenceName});
var body = APP.translation.translateString("email.body",
{appName:interfaceConfig.APP_NAME, sharedKeyText: sharedKeyText,
roomUrl: roomUrl, supportedBrowsers: supportedBrowsers});
body = body.replace(/\n/g, "%0D%0A");
if (window.localStorage.displayname) {
body += "%0D%0A%0D%0A" + window.localStorage.displayname;
}
if (interfaceConfig.INVITATION_POWERED_BY) {
body += "%0D%0A%0D%0A--%0D%0Apowered by jitsi.org";
}
window.open("mailto:?subject=" + subject + "&body=" + body, '_blank');
}
};
const defaultToolbarButtons = {
'microphone': '#toolbar_button_mute',
'camera': '#toolbar_button_camera',
'desktop': '#toolbar_button_desktopsharing',
'security': '#toolbar_button_security',
'invite': '#toolbar_button_link',
'chat': '#toolbar_button_chat',
'prezi': '#toolbar_button_prezi',
'etherpad': '#toolbar_button_etherpad',
'fullscreen': '#toolbar_button_fullScreen',
'settings': '#toolbar_button_settings',
'hangup': '#toolbar_button_hangup'
};
function dialpadButtonClicked() {
//TODO show the dialpad box
}
function callSipButtonClicked() {
var defaultNumber
= config.defaultSipNumber ? config.defaultSipNumber : '';
function showSipNumberInput () {
let defaultNumber = config.defaultSipNumber
? config.defaultSipNumber
: '';
var sipMsg = APP.translation.generateTranslationHTML(
"dialog.sipMsg");
messageHandler.openTwoButtonDialog(null, null, null,
'<h2>' + sipMsg + '</h2>' +
'<input name="sipNumber" type="text"' +
' value="' + defaultNumber + '" autofocus>',
false,
"dialog.Dial",
let sipMsg = APP.translation.generateTranslationHTML("dialog.sipMsg");
messageHandler.openTwoButtonDialog(
null, null, null,
`<h2>${sipMsg}</h2>
<input name="sipNumber" type="text" value="${defaultNumber}" autofocus>`,
false, "dialog.Dial",
function (e, v, m, f) {
if (v) {
var numberInput = f.sipNumber;
if (numberInput) {
APP.xmpp.dial(
numberInput, 'fromnumber', APP.UI.getRoomName(), sharedKey);
}
if (v && f.sipNumber) {
emitter.emit(UIEvents.SIP_DIAL, f.sipNumber);
}
},
null, null, ':input:first'
);
}
var Toolbar = (function (my) {
my.init = function (eventEmitter) {
const Toolbar = {
init (eventEmitter) {
emitter = eventEmitter;
UIUtil.hideDisabledButtons(defaultToolbarButtons);
for(var k in buttonHandlers)
$("#" + k).click(buttonHandlers[k]);
// Update login info
APP.xmpp.addListener(
AuthenticationEvents.IDENTITY_UPDATED,
function (authenticationEnabled, userIdentity) {
var loggedIn = false;
if (userIdentity) {
loggedIn = true;
}
Toolbar.showAuthenticateButton(authenticationEnabled);
if (authenticationEnabled) {
Toolbar.setAuthenticatedIdentity(userIdentity);
Toolbar.showLoginButton(!loggedIn);
Toolbar.showLogoutButton(loggedIn);
}
}
Object.keys(buttonHandlers).forEach(
buttonId => $(`#${buttonId}`).click(buttonHandlers[buttonId])
);
};
/**
* Sets shared key
* @param sKey the shared key
*/
my.setSharedKey = function (sKey) {
sharedKey = sKey;
};
my.authenticateClicked = function () {
Authentication.focusAuthenticationWindow();
if (!APP.xmpp.isExternalAuthEnabled()) {
Authentication.xmppAuthenticate();
return;
}
// Get authentication URL
if (!APP.xmpp.isMUCJoined()) {
APP.xmpp.getLoginUrl(APP.UI.getRoomName(), function (url) {
// If conference has not been started yet - redirect to login page
window.location.href = url;
});
} else {
APP.xmpp.getPopupLoginUrl(APP.UI.getRoomName(), function (url) {
// Otherwise - open popup with authentication URL
var authenticationWindow = Authentication.createAuthenticationWindow(
function () {
// On popup closed - retry room allocation
APP.xmpp.allocateConferenceFocus(
APP.UI.getRoomName(),
function () { console.info("AUTH DONE"); }
);
}, url);
if (!authenticationWindow) {
messageHandler.openMessageDialog(
null, "dialog.popupError");
}
});
}
};
},
/**
* Updates the room invite url.
*/
my.updateRoomUrl = function (newRoomUrl) {
updateRoomUrl (newRoomUrl) {
roomUrl = newRoomUrl;
// If the invite dialog has been already opened we update the information.
var inviteLink = document.getElementById('inviteLinkRef');
let inviteLink = document.getElementById('inviteLinkRef');
if (inviteLink) {
inviteLink.value = roomUrl;
inviteLink.select();
$('#inviteLinkRef').parent()
.find('button[value=true]').prop('disabled', false);
}
};
},
/**
* Disables and enables some of the buttons.
*/
my.setupButtonsFromConfig = function () {
setupButtonsFromConfig () {
if (UIUtil.isButtonEnabled('prezi')) {
$("#toolbar_button_prezi").css({display: "none"});
}
};
},
/**
* Opens the lock room dialog.
*/
my.openLockDialog = function () {
// Only the focus is able to set a shared key.
if (!APP.xmpp.isModerator()) {
if (sharedKey) {
messageHandler.openMessageDialog(null,
"dialog.passwordError");
} else {
messageHandler.openMessageDialog(null, "dialog.passwordError2");
}
} else {
if (sharedKey) {
messageHandler.openTwoButtonDialog(null, null,
"dialog.passwordCheck",
null,
false,
"dialog.Remove",
function (e, v) {
if (v) {
Toolbar.setSharedKey('');
lockRoom(false);
}
});
} else {
var msg = APP.translation.generateTranslationHTML(
"dialog.passwordMsg");
var yourPassword = APP.translation.translateString(
"dialog.yourPassword");
messageHandler.openTwoButtonDialog(null, null, null,
'<h2>' + msg + '</h2>' +
'<input name="lockKey" type="text"' +
' data-i18n="[placeholder]dialog.yourPassword" ' +
'placeholder="' + yourPassword + '" autofocus>',
false,
"dialog.Save",
function (e, v, m, f) {
if (v) {
var lockKey = f.lockKey;
if (lockKey) {
Toolbar.setSharedKey(
UIUtil.escapeHtml(lockKey));
lockRoom(true);
}
}
},
null, null, 'input:first'
);
}
}
};
/**
* Opens the invite link dialog.
*/
my.openLinkDialog = function () {
var inviteAttributes;
if (roomUrl === null) {
inviteAttributes = 'data-i18n="[value]roomUrlDefaultMsg" value="' +
APP.translation.translateString("roomUrlDefaultMsg") + '"';
} else {
inviteAttributes = "value=\"" + encodeURI(roomUrl) + "\"";
}
messageHandler.openTwoButtonDialog("dialog.shareLink",
null, null,
'<input id="inviteLinkRef" type="text" ' +
inviteAttributes + ' onclick="this.select();" readonly>',
false,
"dialog.Invite",
function (e, v) {
if (v) {
if (roomUrl) {
inviteParticipants();
}
}
},
function (event) {
if (roomUrl) {
document.getElementById('inviteLinkRef').select();
} else {
if (event && event.target)
$(event.target)
.find('button[value=true]').prop('disabled', true);
}
}
);
};
/**
* Opens the settings dialog.
* FIXME: not used ?
*/
my.openSettingsDialog = function () {
var settings1 = APP.translation.generateTranslationHTML(
"dialog.settings1");
var settings2 = APP.translation.generateTranslationHTML(
"dialog.settings2");
var settings3 = APP.translation.generateTranslationHTML(
"dialog.settings3");
var yourPassword = APP.translation.translateString(
"dialog.yourPassword");
messageHandler.openTwoButtonDialog(null,
'<h2>' + settings1 + '</h2>' +
'<input type="checkbox" id="initMuted">' +
settings2 + '<br/>' +
'<input type="checkbox" id="requireNicknames">' +
settings3 +
'<input id="lockKey" type="text" placeholder="' + yourPassword +
'" data-i18n="[placeholder]dialog.yourPassword" autofocus>',
null,
null,
false,
"dialog.Save",
function () {
document.getElementById('lockKey').focus();
},
function (e, v) {
if (v) {
if ($('#initMuted').is(":checked")) {
// it is checked
}
if ($('#requireNicknames').is(":checked")) {
// it is checked
}
/*
var lockKey = document.getElementById('lockKey');
if (lockKey.value) {
setSharedKey(lockKey.value);
lockRoom(true);
}
*/
}
}
);
};
/**
* Toggles the application in and out of full screen mode
* (a.k.a. presentation mode in Chrome).
*/
my.toggleFullScreen = function () {
var fsElement = document.documentElement;
if (!document.mozFullScreen && !document.webkitIsFullScreen) {
//Enter Full Screen
if (fsElement.mozRequestFullScreen) {
fsElement.mozRequestFullScreen();
}
else {
fsElement.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT);
}
} else {
//Exit Full Screen
if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else {
document.webkitCancelFullScreen();
}
}
};
/**
* Unlocks the lock button state.
*/
my.unlockLockButton = function () {
unlockLockButton () {
if ($("#toolbar_button_security").hasClass("icon-security-locked"))
UIUtil.buttonClick("#toolbar_button_security", "icon-security icon-security-locked");
};
},
/**
* Updates the lock button state to locked.
*/
my.lockLockButton = function () {
lockLockButton () {
if ($("#toolbar_button_security").hasClass("icon-security"))
UIUtil.buttonClick("#toolbar_button_security", "icon-security icon-security-locked");
};
},
/**
* Shows or hides authentication button
* @param show <tt>true</tt> to show or <tt>false</tt> to hide
*/
my.showAuthenticateButton = function (show) {
showAuthenticateButton (show) {
if (UIUtil.isButtonEnabled('authentication') && show) {
$('#authentication').css({display: "inline"});
}
else {
} else {
$('#authentication').css({display: "none"});
}
};
},
// Shows or hides the 'recording' button.
my.showRecordingButton = function (show) {
showRecordingButton (show) {
if (UIUtil.isButtonEnabled('recording') && show) {
$('#toolbar_button_record').css({display: "inline-block"});
}
else {
} else {
$('#toolbar_button_record').css({display: "none"});
}
};
// Sets the state of the recording button
my.setRecordingButtonState = function (recordingState) {
var selector = $('#toolbar_button_record');
if (recordingState === 'on') {
selector.removeClass("icon-recEnable");
selector.addClass("icon-recEnable active");
$("#largeVideo").toggleClass("videoMessageFilter", true);
var recordOnKey = "recording.on";
$('#videoConnectionMessage').attr("data-i18n", recordOnKey);
$('#videoConnectionMessage').text(APP.translation.translateString(recordOnKey));
setTimeout(function(){
$("#largeVideo").toggleClass("videoMessageFilter", false);
$('#videoConnectionMessage').css({display: "none"});
}, 1500);
recordingToaster = messageHandler.notify(null, "recording.toaster", null,
null, null, {timeOut: 0, closeButton: null, tapToDismiss: false});
} else if (recordingState === 'off') {
selector.removeClass("icon-recEnable active");
selector.addClass("icon-recEnable");
$("#largeVideo").toggleClass("videoMessageFilter", false);
$('#videoConnectionMessage').css({display: "none"});
if (recordingToaster)
messageHandler.remove(recordingToaster);
} else if (recordingState === 'pending') {
selector.removeClass("icon-recEnable active");
selector.addClass("icon-recEnable");
$("#largeVideo").toggleClass("videoMessageFilter", true);
var recordPendingKey = "recording.pending";
$('#videoConnectionMessage').attr("data-i18n", recordPendingKey);
$('#videoConnectionMessage').text(APP.translation.translateString(recordPendingKey));
$('#videoConnectionMessage').css({display: "block"});
}
};
},
// checks whether recording is enabled and whether we have params
// to start automatically recording
my.checkAutoRecord = function () {
checkAutoRecord () {
if (UIUtil.isButtonEnabled('recording') && config.autoRecord) {
toggleRecording(config.autoRecordToken);
emitter.emit(UIEvents.RECORDING_TOGGLE, UIUtil.escapeHtml(config.autoRecordToken));
}
};
},
// checks whether desktop sharing is enabled and whether
// we have params to start automatically sharing
my.checkAutoEnableDesktopSharing = function () {
checkAutoEnableDesktopSharing () {
if (UIUtil.isButtonEnabled('desktop')
&& config.autoEnableDesktopSharing) {
APP.desktopsharing.toggleScreenSharing();
}
};
},
// Shows or hides SIP calls button
my.showSipCallButton = function (show) {
if (APP.xmpp.isSipGatewayEnabled() && UIUtil.isButtonEnabled('sip') && show) {
showSipCallButton (show) {
if (APP.conference.sipGatewayEnabled && UIUtil.isButtonEnabled('sip') && show) {
$('#toolbar_button_sip').css({display: "inline-block"});
} else {
$('#toolbar_button_sip').css({display: "none"});
}
};
},
// Shows or hides the dialpad button
my.showDialPadButton = function (show) {
showDialPadButton (show) {
if (UIUtil.isButtonEnabled('dialpad') && show) {
$('#toolbar_button_dialpad').css({display: "inline-block"});
} else {
$('#toolbar_button_dialpad').css({display: "none"});
}
};
},
/**
* Displays user authenticated identity name(login).
* @param authIdentity identity name to be displayed.
*/
my.setAuthenticatedIdentity = function (authIdentity) {
setAuthenticatedIdentity (authIdentity) {
if (authIdentity) {
var selector = $('#toolbar_auth_identity');
let selector = $('#toolbar_auth_identity');
selector.css({display: "list-item"});
selector.text(authIdentity);
} else {
$('#toolbar_auth_identity').css({display: "none"});
}
};
},
/**
* Shows/hides login button.
* @param show <tt>true</tt> to show
*/
my.showLoginButton = function (show) {
showLoginButton (show) {
if (UIUtil.isButtonEnabled('authentication') && show) {
$('#toolbar_button_login').css({display: "list-item"});
} else {
$('#toolbar_button_login').css({display: "none"});
}
};
},
/**
* Shows/hides logout button.
* @param show <tt>true</tt> to show
*/
my.showLogoutButton = function (show) {
showLogoutButton (show) {
if (UIUtil.isButtonEnabled('authentication') && show) {
$('#toolbar_button_logout').css({display: "list-item"});
} else {
$('#toolbar_button_logout').css({display: "none"});
}
};
},
/**
* Sets the state of the button. The button has blue glow if desktop
* streaming is active.
* @param active the state of the desktop streaming.
*/
my.changeDesktopSharingButtonState = function (active) {
var button = $("#toolbar_button_desktopsharing");
changeDesktopSharingButtonState (active) {
let button = $("#toolbar_button_desktopsharing");
if (active) {
button.addClass("glow");
} else {
button.removeClass("glow");
}
};
},
return my;
}(Toolbar || {}));
updateRecordingState (state) {
setRecordingButtonState(state);
}
};
module.exports = Toolbar;
export default Toolbar;

View File

@ -1,8 +1,9 @@
/* global APP, config, $, interfaceConfig */
var toolbarTimeoutObject,
toolbarTimeout = interfaceConfig.INITIAL_TOOLBAR_TIMEOUT,
UIUtil = require("../util/UIUtil");
import UIUtil from '../util/UIUtil';
let toolbarTimeoutObject;
let toolbarTimeout = interfaceConfig.INITIAL_TOOLBAR_TIMEOUT;
function showDesktopSharingButton() {
if (APP.desktopsharing.isDesktopSharingEnabled() &&
@ -13,19 +14,24 @@ function showDesktopSharingButton() {
}
}
function isToolbarVisible () {
return $('#header').is(':visible');
}
/**
* Hides the toolbar.
*/
function hideToolbar() {
if(config.alwaysVisibleToolbar)
if (config.alwaysVisibleToolbar) {
return;
}
var header = $("#header"),
bottomToolbar = $("#bottomToolbar");
var isToolbarHover = false;
let header = $("#header");
let bottomToolbar = $("#bottomToolbar");
let isToolbarHover = false;
header.find('*').each(function () {
var id = $(this).attr('id');
if ($("#" + id + ":hover").length > 0) {
let id = $(this).attr('id');
if ($(`#${id}:hover`).length > 0) {
isToolbarHover = true;
}
});
@ -36,34 +42,36 @@ function hideToolbar() {
clearTimeout(toolbarTimeoutObject);
toolbarTimeoutObject = null;
if (!isToolbarHover) {
if (isToolbarHover) {
toolbarTimeoutObject = setTimeout(hideToolbar, toolbarTimeout);
} else {
header.hide("slide", { direction: "up", duration: 300});
$('#subject').animate({top: "-=40"}, 300);
if ($("#remoteVideos").hasClass("hidden")) {
bottomToolbar.hide(
"slide", {direction: "right", duration: 300});
"slide", {direction: "right", duration: 300}
);
}
}
else {
toolbarTimeoutObject = setTimeout(hideToolbar, toolbarTimeout);
}
}
var ToolbarToggler = {
const ToolbarToggler = {
/**
* Shows the main toolbar.
*/
showToolbar: function () {
if (interfaceConfig.filmStripOnly)
showToolbar () {
if (interfaceConfig.filmStripOnly) {
return;
var header = $("#header"),
bottomToolbar = $("#bottomToolbar");
}
let header = $("#header");
let bottomToolbar = $("#bottomToolbar");
if (!header.is(':visible') || !bottomToolbar.is(":visible")) {
header.show("slide", { direction: "up", duration: 300});
$('#subject').animate({top: "+=40"}, 300);
if (!bottomToolbar.is(":visible")) {
bottomToolbar.show(
"slide", {direction: "right", duration: 300});
"slide", {direction: "right", duration: 300}
);
}
if (toolbarTimeoutObject) {
@ -83,33 +91,28 @@ var ToolbarToggler = {
*
* @param isDock indicates what operation to perform
*/
dockToolbar: function (isDock) {
if (interfaceConfig.filmStripOnly)
dockToolbar (isDock) {
if (interfaceConfig.filmStripOnly) {
return;
}
if (isDock) {
// First make sure the toolbar is shown.
if (!$('#header').is(':visible')) {
if (!isToolbarVisible()) {
this.showToolbar();
}
// Then clear the time out, to dock the toolbar.
if (toolbarTimeoutObject) {
clearTimeout(toolbarTimeoutObject);
toolbarTimeoutObject = null;
}
}
else {
if (!$('#header').is(':visible')) {
clearTimeout(toolbarTimeoutObject);
toolbarTimeoutObject = null;
} else {
if (isToolbarVisible()) {
toolbarTimeoutObject = setTimeout(hideToolbar, toolbarTimeout);
} else {
this.showToolbar();
}
else {
toolbarTimeoutObject = setTimeout(hideToolbar, toolbarTimeout);
}
}
},
showDesktopSharingButton: showDesktopSharingButton
}
};
module.exports = ToolbarToggler;
module.exports = ToolbarToggler;

View File

@ -1,13 +1,15 @@
/* global $, config, interfaceConfig */
import PanelToggler from "../side_pannels/SidePanelToggler";
/**
* Created by hristo on 12/22/14.
*/
var UIUtil = module.exports = {
var UIUtil = {
/**
* Returns the available video width.
*/
getAvailableVideoWidth: function (isVisible) {
var PanelToggler = require("../side_pannels/SidePanelToggler");
if(typeof isVisible === "undefined" || isVisible === null)
isVisible = PanelToggler.isVisible();
var rightPanelWidth
@ -112,5 +114,11 @@ var UIUtil = module.exports = {
.filter(function (item) { return item; })
.join(',');
$(selector).hide();
}
};
},
redirect (url) {
window.location.href = url;
}
};
export default UIUtil;

View File

@ -1,13 +1,13 @@
/* global APP, $ */
/* jshint -W101 */
var JitsiPopover = require("../util/JitsiPopover");
import JitsiPopover from "../util/JitsiPopover";
/**
* Constructs new connection indicator.
* @param videoContainer the video container associated with the indicator.
* @constructor
*/
function ConnectionIndicator(videoContainer, jid) {
function ConnectionIndicator(videoContainer, id) {
this.videoContainer = videoContainer;
this.bandwidth = null;
this.packetLoss = null;
@ -16,7 +16,7 @@ function ConnectionIndicator(videoContainer, jid) {
this.resolution = null;
this.transport = [];
this.popover = null;
this.jid = jid;
this.id = id;
this.create();
}
@ -87,7 +87,7 @@ ConnectionIndicator.prototype.generateText = function () {
}
var resolutionValue = null;
if(this.resolution && this.jid) {
if(this.resolution && this.id) {
var keys = Object.keys(this.resolution);
for(var ssrc in this.resolution) {
// skip resolutions for ssrc that don't have this info
@ -99,7 +99,7 @@ ConnectionIndicator.prototype.generateText = function () {
}
}
if(this.jid === null) {
if(this.id === null) {
resolution = "";
if(this.resolution === null || !Object.keys(this.resolution) ||
Object.keys(this.resolution).length === 0) {
@ -144,8 +144,8 @@ ConnectionIndicator.prototype.generateText = function () {
if(this.videoContainer.videoSpanId == "localVideoContainer") {
result += "<div class=\"jitsipopover_showmore\" " +
"onclick = \"APP.UI.connectionIndicatorShowMore('" +
// FIXME: we do not know local jid when this text is generated
//this.jid + "')\" data-i18n='connectionindicator." +
// FIXME: we do not know local id when this text is generated
//this.id + "')\" data-i18n='connectionindicator." +
"local')\" data-i18n='connectionindicator." +
(this.showMoreValue ? "less" : "more") + "'>" +
translate("connectionindicator." + (this.showMoreValue ? "less" : "more")) +
@ -365,7 +365,8 @@ ConnectionIndicator.prototype.updateResolution = function (resolution) {
*/
ConnectionIndicator.prototype.updatePopoverData = function () {
this.popover.updateContent(
"<div class=\"connection_info\">" + this.generateText() + "</div>");
`<div class="connection_info">${this.generateText()}</div>`
);
APP.translation.translateElement($(".connection_info"));
};
@ -385,4 +386,4 @@ ConnectionIndicator.prototype.hideIndicator = function () {
this.popover.forceHide();
};
module.exports = ConnectionIndicator;
export default ConnectionIndicator;

View File

@ -1,11 +1,11 @@
/* global $, APP, Strophe, interfaceConfig */
/* global $, APP, interfaceConfig */
/* jshint -W101 */
var Avatar = require("../avatar/Avatar");
import Avatar from "../avatar/Avatar";
import ToolbarToggler from "../toolbars/ToolbarToggler";
import UIUtil from "../util/UIUtil";
import UIEvents from "../../../service/UI/UIEvents";
var RTCBrowserType = require("../../RTC/RTCBrowserType");
var UIUtil = require("../util/UIUtil");
var UIEvents = require("../../../service/UI/UIEvents");
var xmpp = require("../../xmpp/xmpp");
var ToolbarToggler = require("../toolbars/ToolbarToggler");
// FIXME: With Temasys we have to re-select everytime
//var video = $('#largeVideo');
@ -37,22 +37,22 @@ var state = "video";
* @param state the state.
* @returns {JQuery|*|jQuery|HTMLElement} the container.
*/
function getContainerByState(state)
{
function getContainerByState(state) {
var selector = null;
switch (state)
{
case "video":
selector = "#largeVideoWrapper";
break;
case "etherpad":
selector = "#etherpad>iframe";
break;
case "prezi":
selector = "#presentation>iframe";
break;
switch (state) {
case "video":
selector = "#largeVideoWrapper";
break;
case "etherpad":
selector = "#etherpad>iframe";
break;
case "prezi":
selector = "#presentation>iframe";
break;
default:
return null;
}
return (selector !== null)? $(selector) : null;
return $(selector);
}
/**
@ -72,24 +72,25 @@ function positionVideo(video,
animate) {
if (animate) {
video.animate({
width: width,
height: height,
top: verticalIndent,
bottom: verticalIndent,
left: horizontalIndent,
right: horizontalIndent
},
{
queue: false,
duration: 500
});
width: width,
height: height,
top: verticalIndent,
bottom: verticalIndent,
left: horizontalIndent,
right: horizontalIndent
}, {
queue: false,
duration: 500
});
} else {
video.width(width);
video.height(height);
video.css({ top: verticalIndent + 'px',
bottom: verticalIndent + 'px',
left: horizontalIndent + 'px',
right: horizontalIndent + 'px'});
video.css({
top: verticalIndent,
bottom: verticalIndent,
left: horizontalIndent,
right: horizontalIndent
});
}
}
@ -237,16 +238,13 @@ function getCameraVideoSize(videoWidth,
/**
* Updates the src of the active speaker avatar
* @param jid of the current active speaker
*/
function updateActiveSpeakerAvatarSrc() {
var avatar = $("#activeSpeakerAvatar")[0];
var jid = currentSmallVideo.peerJid;
var url = Avatar.getActiveSpeakerUrl(jid);
if (avatar.src === url)
return;
if (jid) {
avatar.src = url;
let avatar = $("#activeSpeakerAvatar");
let id = currentSmallVideo.id;
let url = Avatar.getActiveSpeakerUrl(id);
if (id && avatar.attr('src') !== url) {
avatar.attr('src', url);
currentSmallVideo.showAvatar();
}
}
@ -263,13 +261,15 @@ function changeVideo(isVisible) {
}
updateActiveSpeakerAvatarSrc();
var largeVideoElement = $('#largeVideo')[0];
let largeVideoElement = $('#largeVideo');
APP.RTC.setVideoSrc(largeVideoElement, currentSmallVideo.getSrc());
currentSmallVideo.stream.attach(largeVideoElement);
var flipX = currentSmallVideo.flipX;
let flipX = currentSmallVideo.flipX;
largeVideoElement.style.transform = flipX ? "scaleX(-1)" : "none";
largeVideoElement.css({
transform: flipX ? "scaleX(-1)" : "none"
});
LargeVideo.updateVideoSizeAndPosition(currentSmallVideo.getVideoType());
@ -369,40 +369,35 @@ var LargeVideo = {
/**
* Returns <tt>true</tt> if the user is currently displayed on large video.
*/
isCurrentlyOnLarge: function (resourceJid) {
return currentSmallVideo && resourceJid &&
currentSmallVideo.getResourceJid() === resourceJid;
isCurrentlyOnLarge: function (id) {
return id && id === this.getId();
},
/**
* Updates the large video with the given new video source.
*/
updateLargeVideo: function (resourceJid, forceUpdate) {
if(!isEnabled)
updateLargeVideo: function (id, forceUpdate) {
if(!isEnabled) {
return;
var newSmallVideo = this.VideoLayout.getSmallVideo(resourceJid);
console.info('hover in ' + resourceJid + ', video: ', newSmallVideo);
}
let newSmallVideo = this.VideoLayout.getSmallVideo(id);
console.info(`hover in ${id} , video: `, newSmallVideo);
if (!newSmallVideo) {
console.error("Small video not found for: " + resourceJid);
console.error("Small video not found for: " + id);
return;
}
if (!LargeVideo.isCurrentlyOnLarge(resourceJid) || forceUpdate) {
if (!LargeVideo.isCurrentlyOnLarge(id) || forceUpdate) {
$('#activeSpeaker').css('visibility', 'hidden');
var oldSmallVideo = null;
if (currentSmallVideo) {
oldSmallVideo = currentSmallVideo;
}
let oldId = this.getId();
currentSmallVideo = newSmallVideo;
var oldJid = null;
if (oldSmallVideo)
oldJid = oldSmallVideo.peerJid;
if (oldJid !== resourceJid) {
// we want the notification to trigger even if userJid is undefined,
if (oldId !== id) {
// we want the notification to trigger even if id is undefined,
// or null.
this.eventEmitter.emit(UIEvents.SELECTED_ENDPOINT, resourceJid);
this.eventEmitter.emit(UIEvents.SELECTED_ENDPOINT, id);
}
// We are doing fadeOut/fadeIn animations on parent div which wraps
// largeVideo, because when Temasys plugin is in use it replaces
@ -443,11 +438,10 @@ var LargeVideo = {
currentSmallVideo.enableDominantSpeaker(false);
}
},
onVideoTypeChanged: function (resourceJid, newVideoType) {
onVideoTypeChanged: function (id, newVideoType) {
if (!isEnabled)
return;
if (LargeVideo.isCurrentlyOnLarge(resourceJid))
{
if (LargeVideo.isCurrentlyOnLarge(id)) {
LargeVideo.updateVideoSizeAndPosition(newVideoType);
this.position(null, null, null, null, true);
@ -562,22 +556,23 @@ var LargeVideo = {
getVideoPosition = isDesktop ? getDesktopVideoPosition :
getCameraVideoPosition;
},
getResourceJid: function () {
return currentSmallVideo ? currentSmallVideo.getResourceJid() : null;
getId: function () {
return currentSmallVideo ? currentSmallVideo.id : null;
},
updateAvatar: function (resourceJid) {
if(!isEnabled)
updateAvatar: function (id) {
if (!isEnabled) {
return;
if (resourceJid === this.getResourceJid()) {
}
if (id === this.getId()) {
updateActiveSpeakerAvatarSrc();
}
},
showAvatar: function (resourceJid, show) {
if (!isEnabled)
showAvatar: function (id, show) {
if (!isEnabled) {
return;
if (this.getResourceJid() === resourceJid && state === "video") {
$("#largeVideoWrapper")
.css("visibility", show ? "hidden" : "visible");
}
if (this.getId() === id && state === "video") {
$("#largeVideoWrapper").css("visibility", show ? "hidden" : "visible");
$('#activeSpeaker').css("visibility", show ? "visible" : "hidden");
return true;
}
@ -721,4 +716,4 @@ var LargeVideo = {
}
};
module.exports = LargeVideo;
export default LargeVideo;

View File

@ -1,8 +1,9 @@
/* global $, interfaceConfig, APP */
var SmallVideo = require("./SmallVideo");
var ConnectionIndicator = require("./ConnectionIndicator");
var UIUtil = require("../util/UIUtil");
var UIEvents = require("../../../service/UI/UIEvents");
import ConnectionIndicator from "./ConnectionIndicator";
import UIUtil from "../util/UIUtil";
import UIEvents from "../../../service/UI/UIEvents";
import SmallVideo from "./SmallVideo";
var LargeVideo = require("./LargeVideo");
var RTCBrowserType = require("../../RTC/RTCBrowserType");
@ -13,7 +14,6 @@ function LocalVideo(VideoLayout, emitter) {
this.VideoLayout = VideoLayout;
this.flipX = true;
this.isLocal = true;
this.peerJid = null;
this.emitter = emitter;
}
@ -125,7 +125,7 @@ LocalVideo.prototype.setDisplayName = function(displayName, key) {
if (e.keyCode === 13) {
e.preventDefault();
$('#editDisplayName').hide();
self.VideoLayout.inputDisplayNameHandler(this.value);
// focusout handler will save display name
}
});
});
@ -143,32 +143,25 @@ LocalVideo.prototype.createConnectionIndicator = function() {
this.connectionIndicator = new ConnectionIndicator(this, null);
};
LocalVideo.prototype.changeVideo = function (stream, isMuted) {
var self = this;
LocalVideo.prototype.changeVideo = function (stream) {
this.stream = stream;
function localVideoClick(event) {
let localVideoClick = (event) => {
// FIXME: with Temasys plugin event arg is not an event, but
// the clicked object itself, so we have to skip this call
if (event.stopPropagation) {
event.stopPropagation();
}
self.VideoLayout.handleVideoThumbClicked(
true,
APP.xmpp.myResource());
}
this.VideoLayout.handleVideoThumbClicked(true, this.id);
};
var localVideoContainerSelector = $('#localVideoContainer');
let localVideoContainerSelector = $('#localVideoContainer');
localVideoContainerSelector.off('click');
localVideoContainerSelector.on('click', localVideoClick);
if(isMuted) {
APP.UI.setVideoMute(true);
return;
}
this.flipX = stream.videoType != "screen";
var localVideo = document.createElement('video');
localVideo.id = 'localVideo_' +
APP.RTC.getStreamID(stream.getOriginalStream());
let localVideo = document.createElement('video');
localVideo.id = 'localVideo_' + stream.getId();
if (!RTCBrowserType.isIExplorer()) {
localVideo.autoplay = true;
localVideo.volume = 0; // is it required if audio is separated ?
@ -192,7 +185,10 @@ LocalVideo.prototype.changeVideo = function (stream, isMuted) {
}
// Attach WebRTC stream
APP.RTC.attachMediaStream(localVideoSelector, stream.getOriginalStream());
stream.attach(localVideoSelector);
// FIXME handle
return;
// Add stream ended handler
APP.RTC.addMediaStreamInactiveHandler(
@ -201,20 +197,12 @@ LocalVideo.prototype.changeVideo = function (stream, isMuted) {
// because <video> element is replaced with <object>
localVideo = $('#' + localVideo.id)[0];
localVideoContainer.removeChild(localVideo);
self.VideoLayout.updateRemovedVideo(APP.xmpp.myResource());
self.VideoLayout.updateRemovedVideo(self.id);
});
};
LocalVideo.prototype.joined = function (jid) {
this.peerJid = jid;
LocalVideo.prototype.joined = function (id) {
this.id = id;
};
LocalVideo.prototype.getResourceJid = function () {
var myResource = APP.xmpp.myResource();
if (!myResource) {
console.error("Requested local resource before we're in the MUC");
}
return myResource;
};
module.exports = LocalVideo;
export default LocalVideo;

View File

@ -1,24 +1,25 @@
/* global $, APP, require, Strophe, interfaceConfig */
var ConnectionIndicator = require("./ConnectionIndicator");
var SmallVideo = require("./SmallVideo");
var AudioLevels = require("../audio_levels/AudioLevels");
var MediaStreamType = require("../../../service/RTC/MediaStreamTypes");
var RTCBrowserType = require("../../RTC/RTCBrowserType");
var UIUtils = require("../util/UIUtil");
var XMPPEvents = require("../../../service/xmpp/XMPPEvents");
/* global $, APP, interfaceConfig */
function RemoteVideo(peerJid, VideoLayout) {
this.peerJid = peerJid;
this.resourceJid = Strophe.getResourceFromJid(peerJid);
this.videoSpanId = 'participant_' + this.resourceJid;
import ConnectionIndicator from './ConnectionIndicator';
import SmallVideo from "./SmallVideo";
import AudioLevels from "../audio_levels/AudioLevels";
import UIUtils from "../util/UIUtil";
import UIEvents from '../../../service/UI/UIEvents';
var RTCBrowserType = require("../../RTC/RTCBrowserType");
function RemoteVideo(id, VideoLayout, emitter) {
this.id = id;
this.emitter = emitter;
this.videoSpanId = `participant_${id}`;
this.VideoLayout = VideoLayout;
this.addRemoteVideoContainer();
this.connectionIndicator = new ConnectionIndicator(
this, this.peerJid);
this.connectionIndicator = new ConnectionIndicator(this, id);
this.setDisplayName();
var nickfield = document.createElement('span');
nickfield.className = "nick";
nickfield.appendChild(document.createTextNode(this.resourceJid));
nickfield.appendChild(document.createTextNode(id));
this.container.appendChild(nickfield);
this.bindHoverHandler();
this.flipX = false;
@ -30,18 +31,19 @@ RemoteVideo.prototype.constructor = RemoteVideo;
RemoteVideo.prototype.addRemoteVideoContainer = function() {
this.container = RemoteVideo.createContainer(this.videoSpanId);
if (APP.xmpp.isModerator())
if (APP.conference.isModerator) {
this.addRemoteVideoMenu();
AudioLevels.updateAudioLevelCanvas(this.peerJid, this.VideoLayout);
}
AudioLevels.updateAudioLevelCanvas(this.id, this.VideoLayout);
return this.container;
};
/**
* Adds the remote video menu element for the given <tt>jid</tt> in the
* Adds the remote video menu element for the given <tt>id</tt> in the
* given <tt>parentElement</tt>.
*
* @param jid the jid indicating the video for which we're adding a menu.
* @param id the id indicating the video for which we're adding a menu.
* @param parentElement the parent element where this menu will be added
*/
@ -60,7 +62,7 @@ if (!interfaceConfig.filmStripOnly) {
var popupmenuElement = document.createElement('ul');
popupmenuElement.className = 'popupmenu';
popupmenuElement.id = 'remote_popupmenu_' + this.getResourceJid();
popupmenuElement.id = `remote_popupmenu_${this.id}`;
spanElement.appendChild(popupmenuElement);
var muteMenuItem = document.createElement('li');
@ -88,7 +90,7 @@ if (!interfaceConfig.filmStripOnly) {
event.preventDefault();
}
var isMute = !!self.isMuted;
APP.xmpp.setMute(self.peerJid, !isMute);
self.emitter.emit(UIEvents.AUDIO_MUTED, !isMute);
popupmenuElement.setAttribute('style', 'display:none;');
@ -117,7 +119,7 @@ if (!interfaceConfig.filmStripOnly) {
"data-i18n='videothumbnail.kick'>&nbsp;</div>";
ejectLinkItem.innerHTML = ejectIndicator + ' ' + ejectText;
ejectLinkItem.onclick = function(){
APP.xmpp.eject(self.peerJid);
self.emitter.emit(UIEvents.USER_KICKED, self.id);
popupmenuElement.setAttribute('style', 'display:none;');
};
@ -157,36 +159,36 @@ RemoteVideo.prototype.removeRemoteStreamElement =
select.remove();
console.info((isVideo ? "Video" : "Audio") +
" removed " + this.getResourceJid(), select);
" removed " + this.id, select);
if (isVideo)
this.VideoLayout.updateRemovedVideo(this.getResourceJid());
this.VideoLayout.updateRemovedVideo(this.id);
};
/**
* Removes RemoteVideo from the page.
*/
RemoteVideo.prototype.remove = function () {
console.log("Remove thumbnail", this.peerJid);
console.log("Remove thumbnail", this.id);
this.removeConnectionIndicator();
// Make sure that the large video is updated if are removing its
// corresponding small video.
this.VideoLayout.updateRemovedVideo(this.getResourceJid());
this.VideoLayout.updateRemovedVideo(this.id);
// Remove whole container
if (this.container.parentNode)
if (this.container.parentNode) {
this.container.parentNode.removeChild(this.container);
}
};
RemoteVideo.prototype.waitForPlayback = function (sel, stream) {
var webRtcStream = stream.getOriginalStream();
var isVideo = stream.isVideoStream();
var isVideo = stream.isVideoTrack();
if (!isVideo || webRtcStream.id === 'mixedmslabel') {
return;
}
var self = this;
var resourceJid = this.getResourceJid();
// Register 'onplaying' listener to trigger 'videoactive' on VideoLayout
// when video playback starts
@ -198,7 +200,7 @@ RemoteVideo.prototype.waitForPlayback = function (sel, stream) {
if (RTCBrowserType.isTemasysPluginUsed()) {
sel = self.selectVideoElement();
}
self.VideoLayout.videoactive(sel, resourceJid);
self.VideoLayout.videoactive(sel, self.id);
sel[0].onplaying = null;
if (RTCBrowserType.isTemasysPluginUsed()) {
// 'currentTime' is used to check if the video has started
@ -210,39 +212,33 @@ RemoteVideo.prototype.waitForPlayback = function (sel, stream) {
};
RemoteVideo.prototype.addRemoteStreamElement = function (stream) {
if (!this.container)
if (!this.container) {
return;
}
var self = this;
var webRtcStream = stream.getOriginalStream();
var isVideo = stream.isVideoStream();
var streamElement = SmallVideo.createStreamElement(stream);
var newElementId = streamElement.id;
this.stream = stream;
let isVideo = stream.isVideoTrack();
let streamElement = SmallVideo.createStreamElement(stream);
let newElementId = streamElement.id;
// Put new stream element always in front
UIUtils.prependChild(this.container, streamElement);
var sel = $('#' + newElementId);
let sel = $(`#${newElementId}`);
sel.hide();
// If the container is currently visible we attach the stream.
if (!isVideo || (this.container.offsetParent !== null && isVideo)) {
this.waitForPlayback(sel, stream);
APP.RTC.attachMediaStream(sel, webRtcStream);
stream.attach(sel);
}
APP.RTC.addMediaStreamInactiveHandler(
webRtcStream, function () {
console.log('stream ended', this);
self.removeRemoteStreamElement(webRtcStream, isVideo, newElementId);
});
// Add click handler.
var onClickHandler = function (event) {
let onClickHandler = (event) => {
self.VideoLayout.handleVideoThumbClicked(false, self.getResourceJid());
this.VideoLayout.handleVideoThumbClicked(false, this.id);
// On IE we need to populate this handler on video <object>
// and it does not give event instance as an argument,
@ -255,13 +251,14 @@ RemoteVideo.prototype.addRemoteStreamElement = function (stream) {
};
this.container.onclick = onClickHandler;
// reselect
if (RTCBrowserType.isTemasysPluginUsed())
sel = $('#' + newElementId);
sel[0].onclick = onClickHandler;
if (RTCBrowserType.isTemasysPluginUsed()) {
sel = $(`#${newElementId}`);
}
sel.click(onClickHandler);
},
/**
* Show/hide peer container for the given resourceJid.
* Show/hide peer container for the given id.
*/
RemoteVideo.prototype.showPeerContainer = function (state) {
if (!this.container)
@ -294,7 +291,7 @@ RemoteVideo.prototype.showPeerContainer = function (state) {
// We want to be able to pin a participant from the contact list, even
// if he's not in the lastN set!
// ContactList.setClickable(resourceJid, !isHide);
// ContactList.setClickable(id, !isHide);
};
@ -311,12 +308,11 @@ RemoteVideo.prototype.hideConnectionIndicator = function () {
/**
* Updates the remote video menu.
*
* @param jid the jid indicating the video for which we're adding a menu.
* @param id the id indicating the video for which we're adding a menu.
* @param isMuted indicates the current mute state
*/
RemoteVideo.prototype.updateRemoteVideoMenu = function (isMuted) {
var muteMenuItem
= $('#remote_popupmenu_' + this.getResourceJid() + '>li>a.mutelink');
var muteMenuItem = $(`#remote_popupmenu_${this.id}>li>a.mutelink`);
var mutedIndicator = "<i class='icon-mic-disabled'></i>";
@ -423,13 +419,6 @@ RemoteVideo.prototype.removeRemoteVideoMenu = function() {
}
};
RemoteVideo.prototype.getResourceJid = function () {
if (!this.resourceJid) {
console.error("Undefined resource jid");
}
return this.resourceJid;
};
RemoteVideo.createContainer = function (spanId) {
var container = document.createElement('span');
container.id = spanId;
@ -439,4 +428,4 @@ RemoteVideo.createContainer = function (spanId) {
};
module.exports = RemoteVideo;
export default RemoteVideo;

View File

@ -1,14 +1,15 @@
/* global $, APP, require */
/* jshint -W101 */
var Avatar = require("../avatar/Avatar");
var UIUtil = require("../util/UIUtil");
var LargeVideo = require("./LargeVideo");
import Avatar from "../avatar/Avatar";
import UIUtil from "../util/UIUtil";
import LargeVideo from "./LargeVideo";
var RTCBrowserType = require("../../RTC/RTCBrowserType");
var MediaStreamType = require("../../../service/RTC/MediaStreamTypes");
function SmallVideo() {
this.isMuted = false;
this.hasAvatar = false;
this.stream = null;
}
function setVisibility(selector, show) {
@ -106,9 +107,10 @@ SmallVideo.prototype.setPresenceStatus = function (statusMsg) {
* Creates an audio or video element for a particular MediaStream.
*/
SmallVideo.createStreamElement = function (stream) {
var isVideo = stream.isVideoStream();
let isVideo = stream.isVideoTrack();
var element = isVideo ? document.createElement('video')
let element = isVideo
? document.createElement('video')
: document.createElement('audio');
if (isVideo) {
element.setAttribute("muted", "true");
@ -118,8 +120,7 @@ SmallVideo.createStreamElement = function (stream) {
element.autoplay = true;
}
element.id = (isVideo ? 'remoteVideo_' : 'remoteAudio_') +
APP.RTC.getStreamID(stream.getOriginalStream());
element.id = (isVideo ? 'remoteVideo_' : 'remoteAudio_') + stream.getId();
element.onplay = function () {
console.log("(TIME) Render " + (isVideo ? 'video' : 'audio') + ":\t",
@ -145,7 +146,7 @@ SmallVideo.prototype.bindHoverHandler = function () {
// If the video has been "pinned" by the user we want to
// keep the display name on place.
if (!LargeVideo.isLargeVideoVisible() ||
!LargeVideo.isCurrentlyOnLarge(self.getResourceJid()))
!LargeVideo.isCurrentlyOnLarge(self.id))
self.showDisplayName(false);
}
);
@ -237,15 +238,14 @@ SmallVideo.prototype.showVideoIndicator = function(isMuted) {
};
SmallVideo.prototype.enableDominantSpeaker = function (isEnable) {
var resourceJid = this.getResourceJid();
var displayName = resourceJid;
var displayName = this.id;
var nameSpan = $('#' + this.videoSpanId + '>span.displayname');
if (nameSpan.length > 0)
displayName = nameSpan.html();
console.log("UI enable dominant speaker",
displayName,
resourceJid,
this.id,
isEnable);
@ -316,6 +316,8 @@ SmallVideo.prototype.createModeratorIndicatorElement = function () {
};
SmallVideo.prototype.selectVideoElement = function () {
return $('#' + this.videoSpanId).find(videoElem);
// FIXME maybe move this to the library?
var videoElem = APP.RTC.getVideoElementName();
if (!RTCBrowserType.isTemasysPluginUsed()) {
return $('#' + this.videoSpanId).find(videoElem);
@ -367,32 +369,31 @@ SmallVideo.prototype.hasVideo = function () {
*/
SmallVideo.prototype.showAvatar = function (show) {
if (!this.hasAvatar) {
if (this.peerJid) {
if (this.id) {
// Init avatar
this.avatarChanged(Avatar.getThumbUrl(this.peerJid));
this.avatarChanged(Avatar.getThumbUrl(this.id));
} else {
console.error("Unable to init avatar - no peerjid", this);
console.error("Unable to init avatar - no id", this);
return;
}
}
var resourceJid = this.getResourceJid();
var video = this.selectVideoElement();
let video = this.selectVideoElement();
var avatar = $('#avatar_' + resourceJid);
let avatar = $(`#avatar_${this.id}`);
if (show === undefined || show === null) {
if (!this.isLocal &&
!this.VideoLayout.isInLastN(resourceJid)) {
!this.VideoLayout.isInLastN(this.id)) {
show = true;
} else {
// We want to show the avatar when the video is muted or not exists
// that is when 'true' or 'null' is returned
show = APP.RTC.isVideoMuted(this.peerJid) !== false;
show = !this.stream || this.stream.isMuted();
}
}
if (LargeVideo.showAvatar(resourceJid, show)) {
if (LargeVideo.showAvatar(this.id, show)) {
setVisibility(avatar, false);
setVisibility(video, false);
} else {
@ -405,8 +406,7 @@ SmallVideo.prototype.showAvatar = function (show) {
SmallVideo.prototype.avatarChanged = function (thumbUrl) {
var thumbnail = $('#' + this.videoSpanId);
var resourceJid = this.getResourceJid();
var avatar = $('#avatar_' + resourceJid);
var avatar = $('#avatar_' + this.id);
this.hasAvatar = true;
// set the avatar in the thumbnail
@ -415,7 +415,7 @@ SmallVideo.prototype.avatarChanged = function (thumbUrl) {
} else {
if (thumbnail && thumbnail.length > 0) {
avatar = document.createElement('img');
avatar.id = 'avatar_' + resourceJid;
avatar.id = 'avatar_' + this.id;
avatar.className = 'userAvatar';
avatar.src = thumbUrl;
thumbnail.append(avatar);
@ -423,4 +423,4 @@ SmallVideo.prototype.avatarChanged = function (thumbUrl) {
}
};
module.exports = SmallVideo;
export default SmallVideo;

File diff suppressed because it is too large Load Diff

86
modules/connection.js Normal file
View File

@ -0,0 +1,86 @@
/* global APP, JitsiMeetJS, config */
import LoginDialog from './UI/authentication/LoginDialog';
const ConnectionEvents = JitsiMeetJS.events.connection;
const ConnectionErrors = JitsiMeetJS.errors.connection;
function connect(id, password) {
let connection = new JitsiMeetJS.JitsiConnection(null, null, {
hosts: config.hosts,
bosh: config.bosh,
clientNode: config.clientNode
});
return new Promise(function (resolve, reject) {
connection.addEventListener(
ConnectionEvents.CONNECTION_ESTABLISHED, handleConnectionEstablished
);
connection.addEventListener(
ConnectionEvents.CONNECTION_FAILED, handleConnectionFailed
);
function unsubscribe() {
connection.removeEventListener(
ConnectionEvents.CONNECTION_ESTABLISHED,
handleConnectionEstablished
);
connection.removeEventListener(
ConnectionEvents.CONNECTION_FAILED,
handleConnectionFailed
);
}
function handleConnectionEstablished() {
unsubscribe();
resolve(connection);
}
function handleConnectionFailed(err) {
unsubscribe();
console.error("CONNECTION FAILED:", err);
reject(err);
}
connection.connect({id, password});
});
}
function requestAuth() {
return new Promise(function (resolve, reject) {
let authDialog = LoginDialog.showAuthDialog(
function (id, password) {
connect(id, password).then(function (connection) {
authDialog.close();
resolve(connection);
}, function (err) {
if (err === ConnectionErrors.PASSWORD_REQUIRED) {
authDialog.displayError(err);
} else {
authDialog.close();
reject(err);
}
});
}
);
});
}
export function openConnection({id, password, retry}) {
return connect(id, password).catch(function (err) {
if (!retry) {
throw err;
}
if (err === ConnectionErrors.PASSWORD_REQUIRED) {
// do not retry if token is not valid
if (config.token) {
throw err;
} else {
return requestAuth();
}
} else {
throw err;
}
});
}

View File

@ -73,6 +73,7 @@ module.exports = {
init: function () {
// Called when RTC finishes initialization
return;
APP.RTC.addListener(RTCEvents.RTC_READY,
function() {
screenObtainer.init(eventEmitter);

View File

@ -86,6 +86,7 @@ var statistics = {
stopRemote();
},
start: function () {
return;
APP.RTC.addStreamListener(onStreamCreated,
StreamEventTypes.EVENT_TYPE_LOCAL_CREATED);
APP.xmpp.addListener(XMPPEvents.DISPOSE_CONFERENCE,

View File

@ -1,5 +1,5 @@
/* jshint -W101 */
var RTCBrowserType = require("../RTC/RTCBrowserType");
var RTCBrowserType = require('../RTC/RTCBrowserType');
var SDPUtil = {
filter_special_chars: function (text) {

View File

@ -42,7 +42,10 @@
"jshint": "2.8.0",
"precommit-hook": "3.0.0",
"uglify-js": "2.4.24",
"clean-css": "*"
"clean-css": "*",
"babelify": "*",
"babel-preset-es2015": "*",
"babel-polyfill": "*"
},
"license": "Apache-2.0",
"scripts": {
@ -54,9 +57,15 @@
],
"browserify": {
"transform": [
"browserify-shim"
"browserify-shim",
["babelify", {
"ignore": "node_modules"
}]
]
},
"babel": {
"presets": ["es2015"]
},
"browser": {
"jquery": "./node_modules/jquery/dist/jquery.js",
"jquery-ui": "./node_modules/jquery-ui/jquery-ui.js",

View File

@ -1,4 +1,4 @@
var UIEvents = {
export default {
NICKNAME_CHANGED: "UI.nickname_changed",
SELECTED_ENDPOINT: "UI.selected_endpoint",
PINNED_ENDPOINT: "UI.pinned_endpoint",
@ -21,10 +21,20 @@ var UIEvents = {
START_MUTED_CHANGED: "UI.start_muted_changed",
AUDIO_MUTED: "UI.audio_muted",
VIDEO_MUTED: "UI.video_muted",
/**
* Notifies interested parties when the film strip (remote video's panel)
* is hidden (toggled) or shown (un-toggled).
*/
FILM_STRIP_TOGGLED: "UI.filmstrip_toggled"
PREZI_CLICKED: "UI.prezi_clicked",
ETHERPAD_CLICKED: "UI.etherpad_clicked",
ROOM_LOCK_CLICKED: "UI.room_lock_clicked",
USER_INVITED: "UI.user_invited",
USER_KICKED: "UI.user_kicked",
FULLSCREEN_TOGGLE: "UI.fullscreen_toggle",
AUTH_CLICKED: "UI.auth_clicked",
TOGGLE_CHAT: "UI.toggle_chat",
TOGGLE_SETTINGS: "UI.toggle_settings",
TOGGLE_CONTACT_LIST: "UI.toggle_contact_list",
TOGGLE_FILM_STRIP: "UI.toggle_film_strip",
CONTACT_CLICKED: "UI.contact_clicked",
HANGUP: "UI.hangup",
LOGOUT: "UI.logout",
RECORDING_TOGGLE: "UI.recording_toggle",
TOPIC_CHANGED: "UI.topic_changed"
};
module.exports = UIEvents;