Merge branch 'master' into talk-muted
This commit is contained in:
commit
c95a8e058c
|
@ -1,4 +1,5 @@
|
||||||
node_modules
|
node_modules
|
||||||
|
.DS_Store
|
||||||
*.swp
|
*.swp
|
||||||
.idea/
|
.idea/
|
||||||
*.iml
|
*.iml
|
||||||
|
|
13
app.js
13
app.js
|
@ -8,10 +8,14 @@ import "jquery-ui";
|
||||||
import "strophe";
|
import "strophe";
|
||||||
import "strophe-disco";
|
import "strophe-disco";
|
||||||
import "strophe-caps";
|
import "strophe-caps";
|
||||||
import "tooltip";
|
|
||||||
import "popover";
|
|
||||||
import "jQuery-Impromptu";
|
import "jQuery-Impromptu";
|
||||||
import "autosize";
|
import "autosize";
|
||||||
|
|
||||||
|
import 'aui';
|
||||||
|
import 'aui-experimental';
|
||||||
|
import 'aui-css';
|
||||||
|
import 'aui-experimental-css';
|
||||||
|
|
||||||
window.toastr = require("toastr");
|
window.toastr = require("toastr");
|
||||||
|
|
||||||
import URLProcessor from "./modules/config/URLProcessor";
|
import URLProcessor from "./modules/config/URLProcessor";
|
||||||
|
@ -106,6 +110,11 @@ function init() {
|
||||||
var isUIReady = APP.UI.start();
|
var isUIReady = APP.UI.start();
|
||||||
if (isUIReady) {
|
if (isUIReady) {
|
||||||
APP.conference.init({roomName: buildRoomName()}).then(function () {
|
APP.conference.init({roomName: buildRoomName()}).then(function () {
|
||||||
|
let server = APP.tokenData.server;
|
||||||
|
if(server) {
|
||||||
|
APP.conference.logEvent("server." + server, 1);
|
||||||
|
}
|
||||||
|
|
||||||
APP.UI.initConference();
|
APP.UI.initConference();
|
||||||
|
|
||||||
APP.UI.addListener(UIEvents.LANG_CHANGED, function (language) {
|
APP.UI.addListener(UIEvents.LANG_CHANGED, function (language) {
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet" href="css/all.css"/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="redirectPageMessage">Sorry! You are not allowed to be here :(</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,8 @@
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet" href="css/all.css"/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="redirectPageMessage">Thank you for your feedback!</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
263
conference.js
263
conference.js
|
@ -40,7 +40,14 @@ let connectionIsInterrupted = false;
|
||||||
*/
|
*/
|
||||||
let DSExternalInstallationInProgress = false;
|
let DSExternalInstallationInProgress = false;
|
||||||
|
|
||||||
import {VIDEO_CONTAINER_TYPE} from "./modules/UI/videolayout/LargeVideo";
|
/**
|
||||||
|
* Listens whether conference had been left from local user when we are trying
|
||||||
|
* to navigate away from current page.
|
||||||
|
* @type {ConferenceLeftListener}
|
||||||
|
*/
|
||||||
|
let conferenceLeftListener = null;
|
||||||
|
|
||||||
|
import {VIDEO_CONTAINER_TYPE} from "./modules/UI/videolayout/VideoContainer";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Known custom conference commands.
|
* Known custom conference commands.
|
||||||
|
@ -203,8 +210,28 @@ function muteLocalVideo (muted) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the welcome page is enabled and redirects to it.
|
* Check if the welcome page is enabled and redirects to it.
|
||||||
|
* If requested show a thank you dialog before that.
|
||||||
|
* If we have a close page enabled, redirect to it without
|
||||||
|
* showing any other dialog.
|
||||||
|
* @param {boolean} showThankYou whether we should show a thank you dialog
|
||||||
*/
|
*/
|
||||||
function maybeRedirectToWelcomePage() {
|
function maybeRedirectToWelcomePage(showThankYou) {
|
||||||
|
|
||||||
|
// if close page is enabled redirect to it, without further action
|
||||||
|
if (config.enableClosePage) {
|
||||||
|
window.location.pathname = "close.html";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showThankYou) {
|
||||||
|
APP.UI.messageHandler.openMessageDialog(
|
||||||
|
null, null, null,
|
||||||
|
APP.translation.translateString(
|
||||||
|
"dialog.thankYou", {appName:interfaceConfig.APP_NAME}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (!config.enableWelcomePage) {
|
if (!config.enableWelcomePage) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -236,7 +263,7 @@ function disconnectAndShowFeedback(requestFeedback) {
|
||||||
* @param {boolean} [requestFeedback=false] if user feedback should be requested
|
* @param {boolean} [requestFeedback=false] if user feedback should be requested
|
||||||
*/
|
*/
|
||||||
function hangup (requestFeedback = false) {
|
function hangup (requestFeedback = false) {
|
||||||
const errCallback = (f, err) => {
|
const errCallback = (err) => {
|
||||||
|
|
||||||
// If we want to break out the chain in our error handler, it needs
|
// If we want to break out the chain in our error handler, it needs
|
||||||
// to return a rejected promise. In the case of feedback request
|
// to return a rejected promise. In the case of feedback request
|
||||||
|
@ -251,14 +278,69 @@ function hangup (requestFeedback = false) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const disconnect = disconnectAndShowFeedback.bind(null, requestFeedback);
|
const disconnect = disconnectAndShowFeedback.bind(null, requestFeedback);
|
||||||
APP.conference._room.leave()
|
|
||||||
.then(disconnect)
|
|
||||||
.catch(errCallback.bind(null, disconnect))
|
|
||||||
.then(maybeRedirectToWelcomePage)
|
|
||||||
.catch(function(err){
|
|
||||||
console.log(err);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
if (!conferenceLeftListener)
|
||||||
|
conferenceLeftListener = new ConferenceLeftListener();
|
||||||
|
|
||||||
|
// Make sure that leave is resolved successfully and the set the handlers
|
||||||
|
// to be invoked once conference had been left
|
||||||
|
APP.conference._room.leave()
|
||||||
|
.then(conferenceLeftListener.setHandler(disconnect, errCallback))
|
||||||
|
.catch(errCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listens for CONFERENCE_LEFT event so we can check whether it has finished.
|
||||||
|
* The handler will be called once the conference had been left or if it
|
||||||
|
* was already left when we are adding the handler.
|
||||||
|
*/
|
||||||
|
class ConferenceLeftListener {
|
||||||
|
/**
|
||||||
|
* Creates ConferenceLeftListener and start listening for conference
|
||||||
|
* failed event.
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
room.on(ConferenceEvents.CONFERENCE_LEFT,
|
||||||
|
this._handleConferenceLeft.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the conference left event, if we have a handler we invoke it.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_handleConferenceLeft() {
|
||||||
|
this.conferenceLeft = true;
|
||||||
|
|
||||||
|
if (this.handler)
|
||||||
|
this._handleLeave();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the handlers. If we already left the conference invoke them.
|
||||||
|
* @param handler
|
||||||
|
* @param errCallback
|
||||||
|
*/
|
||||||
|
setHandler (handler, errCallback) {
|
||||||
|
this.handler = handler;
|
||||||
|
this.errCallback = errCallback;
|
||||||
|
|
||||||
|
if (this.conferenceLeft)
|
||||||
|
this._handleLeave();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invokes the handlers.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_handleLeave()
|
||||||
|
{
|
||||||
|
this.handler()
|
||||||
|
.catch(this.errCallback)
|
||||||
|
.then(maybeRedirectToWelcomePage)
|
||||||
|
.catch(function(err){
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -294,8 +376,13 @@ function createLocalTracks (options, checkForPermissionPrompt) {
|
||||||
firefox_fake_device: config.firefox_fake_device,
|
firefox_fake_device: config.firefox_fake_device,
|
||||||
desktopSharingExtensionExternalInstallation:
|
desktopSharingExtensionExternalInstallation:
|
||||||
options.desktopSharingExtensionExternalInstallation
|
options.desktopSharingExtensionExternalInstallation
|
||||||
}, checkForPermissionPrompt)
|
}, checkForPermissionPrompt).then( (tracks) => {
|
||||||
.catch(function (err) {
|
tracks.forEach((track) => {
|
||||||
|
track.on(TrackEvents.NO_DATA_FROM_SOURCE,
|
||||||
|
APP.UI.showTrackNotWorkingDialog.bind(null, track));
|
||||||
|
});
|
||||||
|
return tracks;
|
||||||
|
}).catch(function (err) {
|
||||||
console.error(
|
console.error(
|
||||||
'failed to create local tracks', options.devices, err);
|
'failed to create local tracks', options.devices, err);
|
||||||
return Promise.reject(err);
|
return Promise.reject(err);
|
||||||
|
@ -358,6 +445,14 @@ class ConferenceConnector {
|
||||||
case ConferenceErrors.PASSWORD_REQUIRED:
|
case ConferenceErrors.PASSWORD_REQUIRED:
|
||||||
APP.UI.markRoomLocked(true);
|
APP.UI.markRoomLocked(true);
|
||||||
roomLocker.requirePassword().then(function () {
|
roomLocker.requirePassword().then(function () {
|
||||||
|
let pass = roomLocker.password;
|
||||||
|
// we received that password is required, but user is trying
|
||||||
|
// anyway to login without a password, mark room as not locked
|
||||||
|
// in case he succeeds (maybe someone removed the password
|
||||||
|
// meanwhile), if it is still locked another password required
|
||||||
|
// will be received and the room again will be marked as locked
|
||||||
|
if (!pass)
|
||||||
|
APP.UI.markRoomLocked(false);
|
||||||
room.join(roomLocker.password);
|
room.join(roomLocker.password);
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
@ -369,6 +464,13 @@ class ConferenceConnector {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case ConferenceErrors.NOT_ALLOWED_ERROR:
|
||||||
|
{
|
||||||
|
// let's show some auth not allowed page
|
||||||
|
window.location.pathname = "authError.html";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case ConferenceErrors.VIDEOBRIDGE_NOT_AVAILABLE:
|
case ConferenceErrors.VIDEOBRIDGE_NOT_AVAILABLE:
|
||||||
APP.UI.notifyBridgeDown();
|
APP.UI.notifyBridgeDown();
|
||||||
break;
|
break;
|
||||||
|
@ -649,6 +751,61 @@ export default {
|
||||||
return this._room
|
return this._room
|
||||||
&& this._room.getConnectionState();
|
&& this._room.getConnectionState();
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Checks whether or not our connection is currently in interrupted and
|
||||||
|
* reconnect attempts are in progress.
|
||||||
|
*
|
||||||
|
* @returns {boolean} true if the connection is in interrupted state or
|
||||||
|
* false otherwise.
|
||||||
|
*/
|
||||||
|
isConnectionInterrupted () {
|
||||||
|
return connectionIsInterrupted;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Finds JitsiParticipant for given id.
|
||||||
|
*
|
||||||
|
* @param {string} id participant's identifier(MUC nickname).
|
||||||
|
*
|
||||||
|
* @returns {JitsiParticipant|null} participant instance for given id or
|
||||||
|
* null if not found.
|
||||||
|
*/
|
||||||
|
getParticipantById (id) {
|
||||||
|
return room ? room.getParticipantById(id) : null;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Checks whether the user identified by given id is currently connected.
|
||||||
|
*
|
||||||
|
* @param {string} id participant's identifier(MUC nickname)
|
||||||
|
*
|
||||||
|
* @returns {boolean|null} true if participant's connection is ok or false
|
||||||
|
* if the user is having connectivity issues.
|
||||||
|
*/
|
||||||
|
isParticipantConnectionActive (id) {
|
||||||
|
let participant = this.getParticipantById(id);
|
||||||
|
return participant ? participant.isConnectionActive() : null;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Gets the display name foe the <tt>JitsiParticipant</tt> identified by
|
||||||
|
* the given <tt>id</tt>.
|
||||||
|
*
|
||||||
|
* @param id {string} the participant's id(MUC nickname/JVB endpoint id)
|
||||||
|
*
|
||||||
|
* @return {string} the participant's display name or the default string if
|
||||||
|
* absent.
|
||||||
|
*/
|
||||||
|
getParticipantDisplayName (id) {
|
||||||
|
let displayName = getDisplayName(id);
|
||||||
|
if (displayName) {
|
||||||
|
return displayName;
|
||||||
|
} else {
|
||||||
|
if (APP.conference.isLocalId(id)) {
|
||||||
|
return APP.translation.generateTranslationHTML(
|
||||||
|
interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME);
|
||||||
|
} else {
|
||||||
|
return interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
getMyUserId () {
|
getMyUserId () {
|
||||||
return this._room
|
return this._room
|
||||||
&& this._room.myUserId();
|
&& this._room.myUserId();
|
||||||
|
@ -699,6 +856,30 @@ export default {
|
||||||
return room.getLogs();
|
return room.getLogs();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download logs, a function that can be called from console while
|
||||||
|
* debugging.
|
||||||
|
* @param filename (optional) specify target filename
|
||||||
|
*/
|
||||||
|
saveLogs (filename = 'meetlog.json') {
|
||||||
|
// this can be called from console and will not have reference to this
|
||||||
|
// that's why we reference the global var
|
||||||
|
let logs = APP.conference.getLogs();
|
||||||
|
let data = encodeURIComponent(JSON.stringify(logs, null, ' '));
|
||||||
|
|
||||||
|
let elem = document.createElement('a');
|
||||||
|
|
||||||
|
elem.download = filename;
|
||||||
|
elem.href = 'data:application/json;charset=utf-8,\n' + data;
|
||||||
|
elem.dataset.downloadurl
|
||||||
|
= ['text/json', elem.download, elem.href].join(':');
|
||||||
|
elem.dispatchEvent(new MouseEvent('click', {
|
||||||
|
view: window,
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: false
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Exposes a Command(s) API on this instance. It is necessitated by (1) the
|
* Exposes a Command(s) API on this instance. It is necessitated by (1) the
|
||||||
* desire to keep room private to this instance and (2) the need of other
|
* desire to keep room private to this instance and (2) the need of other
|
||||||
|
@ -858,8 +1039,6 @@ export default {
|
||||||
|
|
||||||
return promise.then(function () {
|
return promise.then(function () {
|
||||||
if (stream) {
|
if (stream) {
|
||||||
stream.on(TrackEvents.TRACK_AUDIO_NOT_WORKING,
|
|
||||||
APP.UI.showAudioNotWorkingDialog);
|
|
||||||
return room.addTrack(stream);
|
return room.addTrack(stream);
|
||||||
}
|
}
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
|
@ -1021,7 +1200,7 @@ export default {
|
||||||
|
|
||||||
console.log('USER %s connnected', id, user);
|
console.log('USER %s connnected', id, user);
|
||||||
APP.API.notifyUserJoined(id);
|
APP.API.notifyUserJoined(id);
|
||||||
APP.UI.addUser(id, user.getDisplayName());
|
APP.UI.addUser(user);
|
||||||
|
|
||||||
// check the roles for the new user and reflect them
|
// check the roles for the new user and reflect them
|
||||||
APP.UI.updateUserRole(user);
|
APP.UI.updateUserRole(user);
|
||||||
|
@ -1037,8 +1216,10 @@ export default {
|
||||||
room.on(ConferenceEvents.USER_ROLE_CHANGED, (id, role) => {
|
room.on(ConferenceEvents.USER_ROLE_CHANGED, (id, role) => {
|
||||||
if (this.isLocalId(id)) {
|
if (this.isLocalId(id)) {
|
||||||
console.info(`My role changed, new role: ${role}`);
|
console.info(`My role changed, new role: ${role}`);
|
||||||
this.isModerator = room.isModerator();
|
if (this.isModerator !== room.isModerator()) {
|
||||||
APP.UI.updateLocalRole(room.isModerator());
|
this.isModerator = room.isModerator();
|
||||||
|
APP.UI.updateLocalRole(room.isModerator());
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
let user = room.getParticipantById(id);
|
let user = room.getParticipantById(id);
|
||||||
if (user) {
|
if (user) {
|
||||||
|
@ -1115,6 +1296,11 @@ export default {
|
||||||
ConferenceEvents.LAST_N_ENDPOINTS_CHANGED, (ids, enteringIds) => {
|
ConferenceEvents.LAST_N_ENDPOINTS_CHANGED, (ids, enteringIds) => {
|
||||||
APP.UI.handleLastNEndpoints(ids, enteringIds);
|
APP.UI.handleLastNEndpoints(ids, enteringIds);
|
||||||
});
|
});
|
||||||
|
room.on(
|
||||||
|
ConferenceEvents.PARTICIPANT_CONN_STATUS_CHANGED,
|
||||||
|
(id, isActive) => {
|
||||||
|
APP.UI.participantConnectionStatusChanged(id, isActive);
|
||||||
|
});
|
||||||
room.on(ConferenceEvents.DOMINANT_SPEAKER_CHANGED, (id) => {
|
room.on(ConferenceEvents.DOMINANT_SPEAKER_CHANGED, (id) => {
|
||||||
if (this.isLocalId(id)) {
|
if (this.isLocalId(id)) {
|
||||||
this.isDominantSpeaker = true;
|
this.isDominantSpeaker = true;
|
||||||
|
@ -1146,10 +1332,12 @@ export default {
|
||||||
room.on(ConferenceEvents.CONNECTION_INTERRUPTED, () => {
|
room.on(ConferenceEvents.CONNECTION_INTERRUPTED, () => {
|
||||||
connectionIsInterrupted = true;
|
connectionIsInterrupted = true;
|
||||||
ConnectionQuality.updateLocalConnectionQuality(0);
|
ConnectionQuality.updateLocalConnectionQuality(0);
|
||||||
|
APP.UI.showLocalConnectionInterrupted(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
room.on(ConferenceEvents.CONNECTION_RESTORED, () => {
|
room.on(ConferenceEvents.CONNECTION_RESTORED, () => {
|
||||||
connectionIsInterrupted = false;
|
connectionIsInterrupted = false;
|
||||||
|
APP.UI.showLocalConnectionInterrupted(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
room.on(ConferenceEvents.DISPLAY_NAME_CHANGED, (id, displayName) => {
|
room.on(ConferenceEvents.DISPLAY_NAME_CHANGED, (id, displayName) => {
|
||||||
|
@ -1173,6 +1361,7 @@ export default {
|
||||||
console.log("Received channel password lock change: ", state,
|
console.log("Received channel password lock change: ", state,
|
||||||
error);
|
error);
|
||||||
APP.UI.markRoomLocked(state);
|
APP.UI.markRoomLocked(state);
|
||||||
|
roomLocker.lockedElsewhere = state;
|
||||||
});
|
});
|
||||||
|
|
||||||
room.on(ConferenceEvents.USER_STATUS_CHANGED, function (id, status) {
|
room.on(ConferenceEvents.USER_STATUS_CHANGED, function (id, status) {
|
||||||
|
@ -1300,15 +1489,6 @@ export default {
|
||||||
&& APP.UI.notifyInitiallyMuted();
|
&& APP.UI.notifyInitiallyMuted();
|
||||||
});
|
});
|
||||||
|
|
||||||
APP.UI.addListener(UIEvents.USER_INVITED, (roomUrl) => {
|
|
||||||
APP.UI.inviteParticipants(
|
|
||||||
roomUrl,
|
|
||||||
APP.conference.roomName,
|
|
||||||
roomLocker.password,
|
|
||||||
APP.settings.getDisplayName()
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
room.on(
|
room.on(
|
||||||
ConferenceEvents.AVAILABLE_DEVICES_CHANGED, function (id, devices) {
|
ConferenceEvents.AVAILABLE_DEVICES_CHANGED, function (id, devices) {
|
||||||
APP.UI.updateDevicesAvailability(id, devices);
|
APP.UI.updateDevicesAvailability(id, devices);
|
||||||
|
@ -1395,6 +1575,8 @@ export default {
|
||||||
|
|
||||||
APP.UI.addListener(UIEvents.PINNED_ENDPOINT, (smallVideo, isPinned) => {
|
APP.UI.addListener(UIEvents.PINNED_ENDPOINT, (smallVideo, isPinned) => {
|
||||||
var smallVideoId = smallVideo.getId();
|
var smallVideoId = smallVideo.getId();
|
||||||
|
// FIXME why VIDEO_CONTAINER_TYPE instead of checking if
|
||||||
|
// the participant is on the large video ?
|
||||||
if (smallVideo.getVideoType() === VIDEO_CONTAINER_TYPE
|
if (smallVideo.getVideoType() === VIDEO_CONTAINER_TYPE
|
||||||
&& !APP.conference.isLocalId(smallVideoId)) {
|
&& !APP.conference.isLocalId(smallVideoId)) {
|
||||||
|
|
||||||
|
@ -1422,7 +1604,7 @@ export default {
|
||||||
.then(([stream]) => {
|
.then(([stream]) => {
|
||||||
this.useVideoStream(stream);
|
this.useVideoStream(stream);
|
||||||
console.log('switched local video device');
|
console.log('switched local video device');
|
||||||
APP.settings.setCameraDeviceId(cameraDeviceId);
|
APP.settings.setCameraDeviceId(cameraDeviceId, true);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
APP.UI.showDeviceErrorDialog(null, err);
|
APP.UI.showDeviceErrorDialog(null, err);
|
||||||
|
@ -1444,7 +1626,7 @@ export default {
|
||||||
.then(([stream]) => {
|
.then(([stream]) => {
|
||||||
this.useAudioStream(stream);
|
this.useAudioStream(stream);
|
||||||
console.log('switched local audio device');
|
console.log('switched local audio device');
|
||||||
APP.settings.setMicDeviceId(micDeviceId);
|
APP.settings.setMicDeviceId(micDeviceId, true);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
APP.UI.showDeviceErrorDialog(err, null);
|
APP.UI.showDeviceErrorDialog(err, null);
|
||||||
|
@ -1539,13 +1721,13 @@ export default {
|
||||||
// storage and settings menu. This is a workaround until
|
// storage and settings menu. This is a workaround until
|
||||||
// getConstraints() method will be implemented in browsers.
|
// getConstraints() method will be implemented in browsers.
|
||||||
if (localAudio) {
|
if (localAudio) {
|
||||||
localAudio._setRealDeviceIdFromDeviceList(devices);
|
APP.settings.setMicDeviceId(
|
||||||
APP.settings.setMicDeviceId(localAudio.getDeviceId());
|
localAudio.getDeviceId(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (localVideo) {
|
if (localVideo) {
|
||||||
localVideo._setRealDeviceIdFromDeviceList(devices);
|
APP.settings.setCameraDeviceId(
|
||||||
APP.settings.setCameraDeviceId(localVideo.getDeviceId());
|
localVideo.getDeviceId(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
mediaDeviceHelper.setCurrentMediaDevices(devices);
|
mediaDeviceHelper.setCurrentMediaDevices(devices);
|
||||||
|
@ -1646,11 +1828,28 @@ export default {
|
||||||
setRaisedHand(raisedHand) {
|
setRaisedHand(raisedHand) {
|
||||||
if (raisedHand !== this.isHandRaised)
|
if (raisedHand !== this.isHandRaised)
|
||||||
{
|
{
|
||||||
|
APP.UI.onLocalRaiseHandChanged(raisedHand);
|
||||||
|
|
||||||
this.isHandRaised = raisedHand;
|
this.isHandRaised = raisedHand;
|
||||||
// Advertise the updated status
|
// Advertise the updated status
|
||||||
room.setLocalParticipantProperty("raisedHand", raisedHand);
|
room.setLocalParticipantProperty("raisedHand", raisedHand);
|
||||||
// Update the view
|
// Update the view
|
||||||
APP.UI.setLocalRaisedHandStatus(raisedHand);
|
APP.UI.setLocalRaisedHandStatus(raisedHand);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Log event to callstats and analytics.
|
||||||
|
* @param {string} name the event name
|
||||||
|
* @param {int} value the value (it's int because google analytics supports
|
||||||
|
* only int).
|
||||||
|
* NOTE: Should be used after conference.init
|
||||||
|
*/
|
||||||
|
logEvent(name, value) {
|
||||||
|
if(JitsiMeetJS.analytics) {
|
||||||
|
JitsiMeetJS.analytics.sendEvent(name, value);
|
||||||
|
}
|
||||||
|
if(room) {
|
||||||
|
room.sendApplicationLog(JSON.stringify({name, value}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -56,6 +56,8 @@ var config = {
|
||||||
//disableAdaptiveSimulcast: false,
|
//disableAdaptiveSimulcast: false,
|
||||||
enableRecording: false,
|
enableRecording: false,
|
||||||
enableWelcomePage: true,
|
enableWelcomePage: true,
|
||||||
|
//enableClosePage: false, // enabling the close page will ignore the welcome
|
||||||
|
// page redirection when call is hangup
|
||||||
disableSimulcast: false,
|
disableSimulcast: false,
|
||||||
logStats: false, // Enable logging of PeerConnection stats via the focus
|
logStats: false, // Enable logging of PeerConnection stats via the focus
|
||||||
// requireDisplayName: true, // Forces the participants that doesn't have display name to enter it when they enter the room.
|
// requireDisplayName: true, // Forces the participants that doesn't have display name to enter it when they enter the room.
|
||||||
|
|
Binary file not shown.
|
@ -81,24 +81,10 @@ form {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
#downloadlog {
|
|
||||||
display: none;
|
|
||||||
position: absolute;
|
|
||||||
bottom: 5;
|
|
||||||
left: 5;
|
|
||||||
overflow: visible;
|
|
||||||
color: rgba(255,255,255,.50);
|
|
||||||
}
|
|
||||||
|
|
||||||
.active {
|
.active {
|
||||||
background-color: #00ccff;
|
background-color: #00ccff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.glow
|
|
||||||
{
|
|
||||||
text-shadow: 0px 0px 30px #06a5df, 0px 0px 10px #06a5df, 0px 0px 5px #06a5df,0px 0px 3px #06a5df;
|
|
||||||
}
|
|
||||||
|
|
||||||
.watermark {
|
.watermark {
|
||||||
display: block;
|
display: block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -175,4 +161,15 @@ form {
|
||||||
display: -ms-flexbox !important;
|
display: -ms-flexbox !important;
|
||||||
display: -webkit-flex !important;
|
display: -webkit-flex !important;
|
||||||
display: flex !important;
|
display: flex !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tipsy {
|
||||||
|
z-index: $tooltipsZ;
|
||||||
|
&-inner {
|
||||||
|
background-color: $tooltipBg;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-arrow {
|
||||||
|
border-color: $tooltipBg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -104,11 +104,6 @@
|
||||||
color: #a7a7a7;
|
color: #a7a7a7;
|
||||||
}
|
}
|
||||||
|
|
||||||
#unreadMessages {
|
|
||||||
font-size: 8px;
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
|
|
||||||
#chat_container .username {
|
#chat_container .username {
|
||||||
float: left;
|
float: left;
|
||||||
padding-left: 5px;
|
padding-left: 5px;
|
||||||
|
|
|
@ -2,12 +2,11 @@
|
||||||
cursor: default;
|
cursor: default;
|
||||||
|
|
||||||
> ul#contacts {
|
> ul#contacts {
|
||||||
position: absolute;
|
font-size: 12px;
|
||||||
top: 31px;
|
|
||||||
bottom: 0px;
|
bottom: 0px;
|
||||||
width: 100%;
|
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
|
width: 100%;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
@ -20,13 +19,13 @@
|
||||||
#contacts {
|
#contacts {
|
||||||
|
|
||||||
>li {
|
>li {
|
||||||
|
color: $defaultSideBarFontColor;
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
color: #FFF;
|
color: #FFF;
|
||||||
font-size: 10pt;
|
font-size: 10pt;
|
||||||
padding: 7px 10px;
|
padding: 6px 10%;
|
||||||
margin: 2px;
|
|
||||||
|
|
||||||
&:hover,
|
&:hover,
|
||||||
&:active {
|
&:active {
|
||||||
|
|
|
@ -255,9 +255,6 @@
|
||||||
.fa-road:before {
|
.fa-road:before {
|
||||||
content: "\f018";
|
content: "\f018";
|
||||||
}
|
}
|
||||||
.fa-download:before {
|
|
||||||
content: "\f019";
|
|
||||||
}
|
|
||||||
.fa-arrow-circle-o-down:before {
|
.fa-arrow-circle-o-down:before {
|
||||||
content: "\f01a";
|
content: "\f01a";
|
||||||
}
|
}
|
||||||
|
@ -842,9 +839,6 @@
|
||||||
.fa-exchange:before {
|
.fa-exchange:before {
|
||||||
content: "\f0ec";
|
content: "\f0ec";
|
||||||
}
|
}
|
||||||
.fa-cloud-download:before {
|
|
||||||
content: "\f0ed";
|
|
||||||
}
|
|
||||||
.fa-cloud-upload:before {
|
.fa-cloud-upload:before {
|
||||||
content: "\f0ee";
|
content: "\f0ee";
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,8 +16,9 @@
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-variant: normal;
|
font-variant: normal;
|
||||||
text-transform: none;
|
text-transform: none;
|
||||||
line-height: 0.75em;
|
line-height: 1.22em;
|
||||||
font-size: 1.22em;
|
font-size: 1.22em;
|
||||||
|
cursor: default;
|
||||||
|
|
||||||
/* Better Font Rendering =========== */
|
/* Better Font Rendering =========== */
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
|
@ -42,9 +43,6 @@
|
||||||
.icon-chat:before {
|
.icon-chat:before {
|
||||||
content: "\e906";
|
content: "\e906";
|
||||||
}
|
}
|
||||||
.icon-download:before {
|
|
||||||
content: "\e902";
|
|
||||||
}
|
|
||||||
.icon-edit:before {
|
.icon-edit:before {
|
||||||
content: "\e907";
|
content: "\e907";
|
||||||
}
|
}
|
||||||
|
@ -57,12 +55,21 @@
|
||||||
.icon-kick:before {
|
.icon-kick:before {
|
||||||
content: "\e904";
|
content: "\e904";
|
||||||
}
|
}
|
||||||
|
.icon-menu-up:before {
|
||||||
|
content: "\e91f";
|
||||||
|
}
|
||||||
|
.icon-menu-down:before {
|
||||||
|
content: "\e920";
|
||||||
|
}
|
||||||
.icon-full-screen:before {
|
.icon-full-screen:before {
|
||||||
content: "\e90b";
|
content: "\e90b";
|
||||||
}
|
}
|
||||||
.icon-exit-full-screen:before {
|
.icon-exit-full-screen:before {
|
||||||
content: "\e90c";
|
content: "\e90c";
|
||||||
}
|
}
|
||||||
|
.icon-star:before {
|
||||||
|
content: "\e916";
|
||||||
|
}
|
||||||
.icon-star-full:before {
|
.icon-star-full:before {
|
||||||
content: "\e90a";
|
content: "\e90a";
|
||||||
}
|
}
|
||||||
|
@ -99,9 +106,6 @@
|
||||||
.icon-settings:before {
|
.icon-settings:before {
|
||||||
content: "\e915";
|
content: "\e915";
|
||||||
}
|
}
|
||||||
.icon-star:before {
|
|
||||||
content: "\e916";
|
|
||||||
}
|
|
||||||
.icon-share-desktop:before {
|
.icon-share-desktop:before {
|
||||||
content: "\e917";
|
content: "\e917";
|
||||||
}
|
}
|
||||||
|
@ -126,6 +130,7 @@
|
||||||
.icon-recEnable:before {
|
.icon-recEnable:before {
|
||||||
content: "\e614";
|
content: "\e614";
|
||||||
}
|
}
|
||||||
|
// FIXME not used anymore - consider removing in the next font update
|
||||||
.icon-presentation:before {
|
.icon-presentation:before {
|
||||||
content: "\e603";
|
content: "\e603";
|
||||||
}
|
}
|
|
@ -9,7 +9,8 @@
|
||||||
}
|
}
|
||||||
div.jqi{
|
div.jqi{
|
||||||
width: 400px;
|
width: 400px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
color: #3a3a3a;
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
|
|
@ -36,6 +36,19 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@mixin circle($diameter) {
|
||||||
|
width: $diameter;
|
||||||
|
height: $diameter;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin absoluteAligning($sizeX, $sizeY) {
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
position: absolute;
|
||||||
|
@include transform(translate(-#{$sizeX / 2}, -#{$sizeY / 2}))
|
||||||
|
}
|
||||||
|
|
||||||
@mixin transform($func) {
|
@mixin transform($func) {
|
||||||
-moz-transform: $func;
|
-moz-transform: $func;
|
||||||
-ms-transform: $func;
|
-ms-transform: $func;
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
html, body {
|
||||||
|
width: 100%;
|
||||||
|
height:100%;
|
||||||
|
color: $defaultColor;
|
||||||
|
background: $defaultBackground;
|
||||||
|
}
|
||||||
|
|
||||||
|
.redirectPageMessage {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 36px;
|
||||||
|
margin-top: 20%;
|
||||||
|
}
|
|
@ -12,12 +12,13 @@
|
||||||
background-color: rgba(0,0,0,0.8);
|
background-color: rgba(0,0,0,0.8);
|
||||||
z-index: 800;
|
z-index: 800;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Labels inside the side panel.
|
* Labels inside the side panel.
|
||||||
*/
|
*/
|
||||||
label {
|
label {
|
||||||
color: $defaultSemiDarkColor;
|
color: $defaultColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -70,16 +71,16 @@
|
||||||
*/
|
*/
|
||||||
> div.title,
|
> div.title,
|
||||||
div.subTitle {
|
div.subTitle {
|
||||||
color: $defaultColor !important;
|
|
||||||
text-align: left;
|
text-align: left;
|
||||||
margin: 10px 0px 10px 0px;
|
margin: 10px 0px 10px 0px;
|
||||||
padding: 5px 10px 5px 10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main title size.
|
* Main title size.
|
||||||
*/
|
*/
|
||||||
> div.title {
|
> div.title {
|
||||||
|
color: $defaultColor !important;
|
||||||
|
text-align: center;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,10 +88,10 @@
|
||||||
* Subtitle specific properties.
|
* Subtitle specific properties.
|
||||||
*/
|
*/
|
||||||
> div.subTitle {
|
> div.subTitle {
|
||||||
font-size: 12px;
|
font-size: 11px;
|
||||||
background: $inputSemiBackground !important;
|
font-weight: 500;
|
||||||
margin-top: 20px !important;
|
color: $defaultSideBarFontColor !important;
|
||||||
margin-bottom: 8px !important;
|
margin-left: 10%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
|
@ -83,14 +83,6 @@
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#numberOfParticipants {
|
|
||||||
position: absolute;
|
|
||||||
top: 5px;
|
|
||||||
line-height: 13px;
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 11px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#mainToolbar a.button:last-child::after {
|
#mainToolbar a.button:last-child::after {
|
||||||
content: none;
|
content: none;
|
||||||
}
|
}
|
||||||
|
@ -118,6 +110,11 @@
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button.glow
|
||||||
|
{
|
||||||
|
text-shadow: 0px 0px 5px $toolbarToggleBackground;
|
||||||
|
}
|
||||||
|
|
||||||
a.button.unclickable:hover,
|
a.button.unclickable:hover,
|
||||||
a.button.unclickable:active,
|
a.button.unclickable:active,
|
||||||
a.button.unclickable.selected{
|
a.button.unclickable.selected{
|
||||||
|
@ -129,6 +126,7 @@ a.button:hover,
|
||||||
a.button:active,
|
a.button:active,
|
||||||
a.button.selected {
|
a.button.selected {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
text-decoration: none;
|
||||||
// sum opacity with background layer should give us 0.8
|
// sum opacity with background layer should give us 0.8
|
||||||
background: $toolbarSelectBackground;
|
background: $toolbarSelectBackground;
|
||||||
}
|
}
|
||||||
|
@ -144,6 +142,36 @@ a.button>#avatar {
|
||||||
margin-top: auto;
|
margin-top: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Round badge.
|
||||||
|
*/
|
||||||
|
.badge-round {
|
||||||
|
background-color: $toolbarBadgeBackground;
|
||||||
|
color: $toolbarBadgeColor;
|
||||||
|
font-size: 9px;
|
||||||
|
line-height: 13px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 50%;
|
||||||
|
min-width: 13px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
box-sizing: border-box;
|
||||||
|
vertical-align: middle;
|
||||||
|
// Do not inherit the font-family from the toolbar button, because it's an
|
||||||
|
// icon style.
|
||||||
|
font-family: $baseFontFamily;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toolbar specific round badge.
|
||||||
|
*/
|
||||||
|
.toolbar .badge-round {
|
||||||
|
position: absolute;
|
||||||
|
right: 9px;
|
||||||
|
bottom: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* START of slide in animation for extended toolbar.
|
* START of slide in animation for extended toolbar.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -10,20 +10,49 @@ $hangupFontSize: 2em;
|
||||||
*/
|
*/
|
||||||
$defaultToolbarSize: 50px;
|
$defaultToolbarSize: 50px;
|
||||||
|
|
||||||
|
// Video layout.
|
||||||
|
$thumbnailIndicatorSize: 23px;
|
||||||
|
$thumbnailIndicatorBorder: 0px;
|
||||||
|
$thumbnailVideoMargin: 2px;
|
||||||
|
$thumbnailToolbarHeight: 25px;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Color variables.
|
* Color variables.
|
||||||
*/
|
*/
|
||||||
$defaultColor: #F1F1F1;
|
$defaultColor: #F1F1F1;
|
||||||
$defaultSemiDarkColor: #ACACAC;
|
$defaultSideBarFontColor: #44A5FF;
|
||||||
$defaultDarkColor: #4F4F4F;
|
$defaultDarkColor: #4F4F4F;
|
||||||
$defaultBackground: #474747;
|
$defaultBackground: #474747;
|
||||||
|
$tooltipBg: rgba(0,0,0, 0.7);
|
||||||
|
|
||||||
|
// Toolbar
|
||||||
$toolbarSelectBackground: rgba(0, 0, 0, .6);
|
$toolbarSelectBackground: rgba(0, 0, 0, .6);
|
||||||
|
|
||||||
|
$toolbarBadgeBackground: #165ECC;
|
||||||
|
$toolbarBadgeColor: #FFFFFF;
|
||||||
|
$toolbarToggleBackground: #165ECC;
|
||||||
|
|
||||||
|
// Main controls
|
||||||
$inputBackground: rgba(132, 132, 132, .5);
|
$inputBackground: rgba(132, 132, 132, .5);
|
||||||
$inputSemiBackground: rgba(132, 132, 132, .8);
|
$inputSemiBackground: rgba(132, 132, 132, .8);
|
||||||
$inputLightBackground: #EBEBEB;
|
$inputLightBackground: #EBEBEB;
|
||||||
$inputBorderColor: #EBEBEB;
|
$inputBorderColor: #EBEBEB;
|
||||||
$buttonBackground: #44A5FF;
|
$buttonBackground: #44A5FF;
|
||||||
|
|
||||||
|
// Video layout.
|
||||||
|
$videoThumbnailHovered: #BFEBFF;
|
||||||
|
$videoThumbnailSelected: #165ECC;
|
||||||
|
$participantNameColor: #fff;
|
||||||
|
$thumbnailPictogramColor: #fff;
|
||||||
|
$dominantSpeakerBg: #165ecc;
|
||||||
|
$raiseHandBg: #D6D61E;
|
||||||
|
$audioLevelBg: #44A5FF;
|
||||||
|
$audioLevelShadow: rgba(9, 36, 77, 0.9);
|
||||||
|
|
||||||
|
$rateStarDefault: #ccc;
|
||||||
|
$rateStarActivity: #165ecc;
|
||||||
|
$rateStarLabelColor: #333;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Misc.
|
* Misc.
|
||||||
*/
|
*/
|
||||||
|
@ -33,9 +62,6 @@ $defaultWatermarkLink: '../images/watermark.png';
|
||||||
/**
|
/**
|
||||||
* Z-indexes. TODO: Replace this by a function.
|
* Z-indexes. TODO: Replace this by a function.
|
||||||
*/
|
*/
|
||||||
|
$tooltipsZ: 901;
|
||||||
$toolbarZ: 900;
|
$toolbarZ: 900;
|
||||||
$overlayZ: 800;
|
$overlayZ: 800;
|
||||||
|
|
||||||
$rateStarDefault: #ccc;
|
|
||||||
$rateStarActivity: #f6c342;
|
|
||||||
$rateStarLabelColor: #333;
|
|
|
@ -1,5 +1,10 @@
|
||||||
|
#videoconference_page {
|
||||||
|
min-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
#videospace {
|
#videospace {
|
||||||
display: block;
|
display: block;
|
||||||
|
min-height: 100%;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0px;
|
top: 0px;
|
||||||
left: 0px;
|
left: 0px;
|
||||||
|
@ -13,19 +18,19 @@
|
||||||
display: -ms-flexbox;
|
display: -ms-flexbox;
|
||||||
display: -webkit-flex;
|
display: -webkit-flex;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row-reverse;
|
||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
justify-content: flex-end;
|
justify-content: flex-start;
|
||||||
|
|
||||||
position:absolute;
|
position:absolute;
|
||||||
text-align:right;
|
text-align:right;
|
||||||
height:196px;
|
height:196px;
|
||||||
padding: 18px;
|
padding: 10px 10px 10px 5px;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 20px;
|
right: 0;
|
||||||
width:auto;
|
width:auto;
|
||||||
border:1px solid transparent;
|
border: 2px solid transparent;
|
||||||
z-index: 5;
|
z-index: 5;
|
||||||
transition: bottom 2s;
|
transition: bottom 2s;
|
||||||
overflow: visible !important;
|
overflow: visible !important;
|
||||||
|
@ -43,33 +48,58 @@
|
||||||
|
|
||||||
#remoteVideos .videocontainer {
|
#remoteVideos .videocontainer {
|
||||||
display: none;
|
display: none;
|
||||||
|
position: relative;
|
||||||
background-color: black;
|
background-color: black;
|
||||||
background-size: contain;
|
background-size: contain;
|
||||||
border-radius:1px;
|
border-radius:1px;
|
||||||
border: 1px solid #212425;
|
margin: 0 $thumbnailVideoMargin;
|
||||||
}
|
}
|
||||||
|
|
||||||
#remoteVideos .videocontainer.videoContainerFocused {
|
/**
|
||||||
|
* The toolbar of the video thumbnail.
|
||||||
|
*/
|
||||||
|
.videocontainer__toolbar {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 1;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box; // Includes the padding in the 100% width.
|
||||||
|
height: $thumbnailToolbarHeight;
|
||||||
|
max-height: 100%;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
padding: 0 5px 0 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#remoteVideos .videocontainer.videoContainerFocused,
|
||||||
|
#remoteVideos .videocontainer:hover {
|
||||||
cursor: hand;
|
cursor: hand;
|
||||||
|
margin-right: $thumbnailVideoMargin - 2;
|
||||||
|
margin-left: $thumbnailVideoMargin - 2;
|
||||||
|
margin-top: -2px;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Focused video thumbnail.
|
||||||
|
*/
|
||||||
|
#remoteVideos .videocontainer.videoContainerFocused {
|
||||||
transition-duration: 0.5s;
|
transition-duration: 0.5s;
|
||||||
-webkit-transition-duration: 0.5s;
|
-webkit-transition-duration: 0.5s;
|
||||||
-webkit-animation-name: greyPulse;
|
-webkit-animation-name: greyPulse;
|
||||||
-webkit-animation-duration: 2s;
|
-webkit-animation-duration: 2s;
|
||||||
-webkit-animation-iteration-count: 1;
|
-webkit-animation-iteration-count: 1;
|
||||||
|
border: 2px solid $videoThumbnailSelected !important;
|
||||||
|
box-shadow: inset 0 0 3px $videoThumbnailSelected,
|
||||||
|
0 0 3px $videoThumbnailSelected !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hovered video thumbnail.
|
||||||
|
*/
|
||||||
#remoteVideos .videocontainer:hover {
|
#remoteVideos .videocontainer:hover {
|
||||||
border: 1px solid #c1c1c1;
|
cursor: hand;
|
||||||
}
|
border: 2px solid $videoThumbnailHovered;
|
||||||
|
box-shadow: inset 0 0 3px $videoThumbnailHovered,
|
||||||
#remoteVideos .videocontainer.videoContainerFocused {
|
0 0 3px $videoThumbnailHovered;
|
||||||
box-shadow: inset 0 0 28px #006d91;
|
|
||||||
border: 1px solid #006d91;
|
|
||||||
}
|
|
||||||
|
|
||||||
#remoteVideos .videocontainer.videoContainerFocused:hover {
|
|
||||||
box-shadow: inset 0 0 5px #c1c1c1, 0 0 10px #c1c1c1, inset 0 0 60px #006d91;
|
|
||||||
border: 1px solid #c1c1c1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#localVideoWrapper {
|
#localVideoWrapper {
|
||||||
|
@ -113,7 +143,6 @@
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
#presentation,
|
|
||||||
#sharedVideo,
|
#sharedVideo,
|
||||||
#etherpad,
|
#etherpad,
|
||||||
#localVideoWrapper>video,
|
#localVideoWrapper>video,
|
||||||
|
@ -132,8 +161,7 @@
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#etherpad,
|
#etherpad {
|
||||||
#presentation {
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,47 +169,36 @@
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#remoteVideos .videocontainer>span.focusindicator,
|
/**
|
||||||
#remoteVideos .videocontainer>div.remotevideomenu {
|
* Positions video thumbnail display name and editor.
|
||||||
position: absolute;
|
*/
|
||||||
color: #FFFFFF;
|
.videocontainer .displayname,
|
||||||
top: 0;
|
.videocontainer .editdisplayname {
|
||||||
left: 0;
|
|
||||||
padding: 5px 0px;
|
|
||||||
width: 25px;
|
|
||||||
font-size: 11pt;
|
|
||||||
text-shadow: 0px 1px 0px rgba(255,255,255,.3), 0px -1px 0px rgba(0,0,0,.7);
|
|
||||||
border: 0px;
|
|
||||||
z-index: 2;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#remoteVideos .videocontainer>span.focusindicator {
|
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
|
||||||
|
|
||||||
#remoteVideos .videocontainer>div.remotevideomenu {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.videocontainer>span.displayname,
|
|
||||||
.videocontainer>input.displayname {
|
|
||||||
display: none;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
color: #FFFFFF;
|
left: 30%;
|
||||||
background: rgba(0,0,0,.7);
|
width: 40%;
|
||||||
|
color: $participantNameColor;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
width: 70%;
|
font-size: 12px;
|
||||||
height: 20%;
|
font-weight: 100;
|
||||||
left: 15%;
|
letter-spacing: 1px;
|
||||||
top: 40%;
|
|
||||||
padding: 5px;
|
|
||||||
font-size: 11pt;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
line-height: $thumbnailToolbarHeight;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
border-radius:3px;
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Positions video thumbnail display name editor.
|
||||||
|
*/
|
||||||
|
.videocontainer .editdisplayname {
|
||||||
|
outline: none;
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
box-shadow: none;
|
||||||
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.videocontainer>span.status {
|
.videocontainer>span.status {
|
||||||
|
@ -221,6 +238,12 @@
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.connection.connection_lost
|
||||||
|
{
|
||||||
|
color: #8B8B8B;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
.connection.connection_full
|
.connection.connection_full
|
||||||
{
|
{
|
||||||
color: #FFFFFF;/*#15A1ED*/
|
color: #FFFFFF;/*#15A1ED*/
|
||||||
|
@ -257,16 +280,16 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
#localVideoContainer>span.status:hover,
|
#localVideoContainer>span.status:hover,
|
||||||
#localVideoContainer>span.displayname:hover {
|
#localVideoContainer .displayname:hover {
|
||||||
cursor: text;
|
cursor: text;
|
||||||
}
|
}
|
||||||
|
|
||||||
.videocontainer>span.status,
|
.videocontainer>span.status,
|
||||||
.videocontainer>span.displayname {
|
.videocontainer .displayname {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.videocontainer>input.displayname {
|
.videocontainer .editdisplayname {
|
||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -287,53 +310,103 @@
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.videocontainer>span.audioMuted {
|
/**
|
||||||
display: inline-block;
|
* Video thumbnail toolbar icon.
|
||||||
position: absolute;
|
*/
|
||||||
color: #FFFFFF;
|
.videocontainer .toolbar-icon {
|
||||||
top: 0;
|
|
||||||
padding: 8px 5px;
|
|
||||||
width: 25px;
|
|
||||||
font-size: 8pt;
|
font-size: 8pt;
|
||||||
text-shadow: 0px 1px 0px rgba(255,255,255,.3), 0px -1px 0px rgba(0,0,0,.7);
|
|
||||||
border: 0px;
|
|
||||||
z-index: 3;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
text-shadow: 0px 1px 0px rgba(255,255,255,.3), 0px -1px 0px rgba(0,0,0,.7);
|
||||||
|
color: #FFFFFF;
|
||||||
|
width: 12px;
|
||||||
|
line-height: $thumbnailToolbarHeight;
|
||||||
|
height: $thumbnailToolbarHeight;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
margin: 0px 5px 0px 0px;
|
||||||
|
float: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.videocontainer>span.videoMuted {
|
/**
|
||||||
display: inline-block;
|
* Toolbar icon internal i elements (font icons).
|
||||||
position: absolute;
|
*/
|
||||||
color: #FFFFFF;
|
.toolbar-icon>i {
|
||||||
top: 0;
|
line-height: $thumbnailToolbarHeight;
|
||||||
right: 0;
|
}
|
||||||
padding: 8px 5px;
|
|
||||||
width: 25px;
|
/**
|
||||||
font-size: 8pt;
|
* Toolbar icons positioned on the right.
|
||||||
text-shadow: 0px 1px 0px rgba(255,255,255,.3), 0px -1px 0px rgba(0,0,0,.7);
|
*/
|
||||||
border: 0px;
|
.toolbar-icon.right {
|
||||||
z-index: 3;
|
float: right;
|
||||||
|
margin: 0px 0px 0px 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.videocontainer>span.indicator {
|
.videocontainer>span.indicator {
|
||||||
bottom: 0px;
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
left: 0px;
|
left: 0px;
|
||||||
width: 25px;
|
@include circle($thumbnailIndicatorSize);
|
||||||
height: 25px;
|
box-sizing: border-box;
|
||||||
|
line-height: $thumbnailIndicatorSize - 2*$thumbnailIndicatorBorder;
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
border-radius: 50%;
|
background: $dominantSpeakerBg;
|
||||||
background: #21B9FC;
|
margin: 7px;
|
||||||
margin: 5px;
|
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
color: $thumbnailPictogramColor;
|
||||||
|
font-size: 8pt;
|
||||||
|
border: $thumbnailIndicatorBorder solid $thumbnailPictogramColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.videocontainer>#raisehandindicator {
|
||||||
|
background: $raiseHandBg;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Audio indicator on video thumbnails.
|
||||||
|
*/
|
||||||
|
.videocontainer>span.audioindicator {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
color: #FFFFFF;
|
display: inline-block;
|
||||||
font-size: 11pt;
|
left: 6px;
|
||||||
border: 0px;
|
top: 50%;
|
||||||
|
margin-top: -17px;
|
||||||
|
width: 6px;
|
||||||
|
height: 35px;
|
||||||
|
z-index: 2;
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
.audiodot-top,
|
||||||
|
.audiodot-bottom,
|
||||||
|
.audiodot-middle {
|
||||||
|
opacity: 0;
|
||||||
|
display: inline-block;
|
||||||
|
@include circle(5px);
|
||||||
|
background: $audioLevelShadow;
|
||||||
|
margin: 1px 0 1px 0;
|
||||||
|
transition: opacity .25s ease-in-out;
|
||||||
|
-moz-transition: opacity .25s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.audiodot-top::after,
|
||||||
|
span.audiodot-bottom::after,
|
||||||
|
span.audiodot-middle::after {
|
||||||
|
content: "";
|
||||||
|
display: inline-block;
|
||||||
|
width: 5px;
|
||||||
|
height: 5px;
|
||||||
|
border-radius: 50%;
|
||||||
|
-webkit-filter: blur(0.5px);
|
||||||
|
filter: blur(0.5px);
|
||||||
|
background: $audioLevelBg;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#indicatoricon {
|
#indicatoricon {
|
||||||
padding-top: 5px;
|
width: $thumbnailIndicatorSize - 2*$thumbnailIndicatorBorder;
|
||||||
|
height: $thumbnailIndicatorSize - 2*$thumbnailIndicatorBorder;
|
||||||
|
line-height: $thumbnailIndicatorSize - 2*$thumbnailIndicatorBorder;
|
||||||
}
|
}
|
||||||
|
|
||||||
#reloadPresentation {
|
#reloadPresentation {
|
||||||
|
@ -366,25 +439,20 @@
|
||||||
width: 300px;
|
width: 300px;
|
||||||
height: 300px;
|
height: 300px;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
overflow: hidden;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
#dominantSpeakerAudioLevel {
|
|
||||||
position: absolute;
|
|
||||||
top: 0px;
|
|
||||||
left: 0px;
|
|
||||||
z-index: 2;
|
|
||||||
visibility: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
#mixedstream {
|
#mixedstream {
|
||||||
display:none !important;
|
display:none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
#dominantSpeakerAvatar {
|
#dominantSpeakerAvatar,
|
||||||
|
.dynamic-shadow {
|
||||||
width: 200px;
|
width: 200px;
|
||||||
height: 200px;
|
height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#dominantSpeakerAvatar {
|
||||||
top: 50px;
|
top: 50px;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -394,11 +462,18 @@
|
||||||
background-color: #000000;
|
background-color: #000000;
|
||||||
}
|
}
|
||||||
|
|
||||||
.userAvatar {
|
.dynamic-shadow {
|
||||||
height: 100%;
|
border-radius: 50%;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0;
|
top: 50%;
|
||||||
border-radius: 2px;
|
left: 50%;
|
||||||
|
margin: -100px 0 0 -100px;
|
||||||
|
transition: box-shadow 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.userAvatar {
|
||||||
|
@include circle(60px);
|
||||||
|
@include absoluteAligning(60px, 60px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.sharedVideoAvatar {
|
.sharedVideoAvatar {
|
||||||
|
@ -436,12 +511,44 @@
|
||||||
filter: grayscale(.5) opacity(0.8);
|
filter: grayscale(.5) opacity(0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.remoteVideoProblemFilter {
|
||||||
|
-webkit-filter: grayscale(100%);
|
||||||
|
filter: grayscale(100%);
|
||||||
|
}
|
||||||
|
|
||||||
.videoProblemFilter {
|
.videoProblemFilter {
|
||||||
-webkit-filter: blur(10px) grayscale(.5) opacity(0.8);
|
-webkit-filter: blur(10px) grayscale(.5) opacity(0.8);
|
||||||
filter: blur(10px) grayscale(.5) opacity(0.8);
|
filter: blur(10px) grayscale(.5) opacity(0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
#videoConnectionMessage {
|
.videoThumbnailProblemFilter {
|
||||||
|
-webkit-filter: grayscale(100%);
|
||||||
|
filter: grayscale(100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
#remoteConnectionMessage {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
width: auto;
|
||||||
|
z-index: 1011;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 14px;
|
||||||
|
text-align: center;
|
||||||
|
color: #FFF;
|
||||||
|
opacity: .80;
|
||||||
|
text-shadow: 0px 0px 1px rgba(0,0,0,0.3),
|
||||||
|
0px 1px 1px rgba(0,0,0,0.3),
|
||||||
|
1px 0px 1px rgba(0,0,0,0.3),
|
||||||
|
0px 0px 1px rgba(0,0,0,0.3);
|
||||||
|
|
||||||
|
background: rgba(0,0,0,.5);
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 5px;
|
||||||
|
padding-left: 10px;
|
||||||
|
padding-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#localConnectionMessage {
|
||||||
display: none;
|
display: none;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
|
@ -22,6 +22,8 @@
|
||||||
@import 'toastr';
|
@import 'toastr';
|
||||||
@import 'base';
|
@import 'base';
|
||||||
@import 'overlay/overlay';
|
@import 'overlay/overlay';
|
||||||
|
@import 'modals/dialog';
|
||||||
|
@import 'modals/feedback/feedback';
|
||||||
@import 'videolayout_default';
|
@import 'videolayout_default';
|
||||||
@import 'jquery-impromptu';
|
@import 'jquery-impromptu';
|
||||||
@import 'modaldialog';
|
@import 'modaldialog';
|
||||||
|
@ -38,8 +40,9 @@
|
||||||
@import 'toolbars';
|
@import 'toolbars';
|
||||||
@import 'side_toolbar_container';
|
@import 'side_toolbar_container';
|
||||||
@import 'device_settings_dialog';
|
@import 'device_settings_dialog';
|
||||||
@import 'feedback';
|
|
||||||
@import 'jquery.contextMenu';
|
@import 'jquery.contextMenu';
|
||||||
@import 'keyboard-shortcuts';
|
@import 'keyboard-shortcuts';
|
||||||
|
@import 'redirect_page';
|
||||||
|
|
||||||
|
|
||||||
/* Modules END */
|
/* Modules END */
|
|
@ -0,0 +1,53 @@
|
||||||
|
.dialog{
|
||||||
|
visibility: visible;
|
||||||
|
height: auto;
|
||||||
|
|
||||||
|
p {
|
||||||
|
color: $defaultDarkColor;
|
||||||
|
}
|
||||||
|
textarea {
|
||||||
|
background: none;
|
||||||
|
border: 1px solid $inputBorderColor;
|
||||||
|
}
|
||||||
|
.aui-dialog2-content:last-child {
|
||||||
|
border-bottom-right-radius: 5px;
|
||||||
|
border-bottom-left-radius: 5px;
|
||||||
|
}
|
||||||
|
.aui-dialog2-content:first-child {
|
||||||
|
border-top-right-radius: 5px;
|
||||||
|
border-top-left-radius: 5px;
|
||||||
|
}
|
||||||
|
.aui-dialog2-footer{
|
||||||
|
border-top: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
padding-top: 0;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
height: auto;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
.aui-button {
|
||||||
|
height: 28px;
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 3px 6px 3px 6px;
|
||||||
|
border: none;
|
||||||
|
box-shadow: none;
|
||||||
|
outline: none;
|
||||||
|
|
||||||
|
&_close {
|
||||||
|
font-weight: 400 !important;
|
||||||
|
color: $buttonBackground;
|
||||||
|
background: none !important;
|
||||||
|
|
||||||
|
:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&_submit {
|
||||||
|
font-weight: 700 !important;
|
||||||
|
color: $defaultColor;
|
||||||
|
background: $buttonBackground;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -33,6 +33,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.shake-rotate {
|
.shake-rotate {
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
-webkit-animation-duration: .4s;
|
-webkit-animation-duration: .4s;
|
||||||
animation-duration: .4s;
|
animation-duration: .4s;
|
||||||
-webkit-animation-iteration-count: infinite;
|
-webkit-animation-iteration-count: infinite;
|
||||||
|
@ -43,65 +45,64 @@
|
||||||
animation-timing-function: ease-in-out
|
animation-timing-function: ease-in-out
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-center {
|
.feedback {
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feedbackDetails textarea {
|
|
||||||
resize: vertical;
|
|
||||||
min-height: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.feedback-rating {
|
|
||||||
line-height: 1.2;
|
|
||||||
padding: 20px 0;
|
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
padding: auto;
|
|
||||||
margin: auto;
|
|
||||||
border: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
margin-top: 10px;
|
font-weight: 400;
|
||||||
margin-left: 0px;
|
font-size: 14px;
|
||||||
margin-bottom: 0px;
|
|
||||||
margin-right: 0px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.star-label {
|
&__content {
|
||||||
font-size: 16px;
|
text-align: center;
|
||||||
color: $rateStarLabelColor;
|
|
||||||
|
textarea {
|
||||||
|
text-align: left;
|
||||||
|
min-height: 80px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
&__footer {
|
||||||
|
|
||||||
.star-btn {
|
|
||||||
color: $rateStarDefault;
|
|
||||||
font-size: 36px;
|
|
||||||
position: relative;
|
|
||||||
cursor: pointer;
|
|
||||||
outline: none;
|
|
||||||
text-decoration: none;
|
|
||||||
@include transition(all .2s ease);
|
|
||||||
|
|
||||||
&.starHover,
|
|
||||||
&.active,
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: $rateStarActivity;
|
color: #287ade;
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&__rating {
|
||||||
|
line-height: 1.2;
|
||||||
|
padding: 20px 0;
|
||||||
|
|
||||||
.fa {
|
p {
|
||||||
top: -6px;
|
margin: 10px 0 0;
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
&.rated:hover .fa {
|
|
||||||
top: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.fa {
|
.star-label {
|
||||||
|
font-size: 16px;
|
||||||
|
color: $rateStarLabelColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.star-btn {
|
||||||
|
color: $rateStarDefault;
|
||||||
|
font-size: 36px;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
outline: none;
|
||||||
|
text-decoration: none;
|
||||||
|
@include transition(all .2s ease);
|
||||||
|
|
||||||
|
&.starHover,
|
||||||
|
&.active,
|
||||||
|
&:hover {
|
||||||
|
color: $rateStarActivity;
|
||||||
|
> i:before {
|
||||||
|
content: "\e90a";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -9,6 +9,11 @@
|
||||||
background: linear-gradient(transparent, #000);
|
background: linear-gradient(transparent, #000);
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
|
|
||||||
|
&.solidBG {
|
||||||
|
background: $defaultBackground;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
&__content {
|
&__content {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 400px;
|
width: 400px;
|
||||||
|
@ -33,4 +38,4 @@
|
||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
BIN
fonts/jitsi.eot
BIN
fonts/jitsi.eot
Binary file not shown.
|
@ -42,4 +42,6 @@
|
||||||
<glyph unicode="" glyph-name="toggle-filmstrip" d="M896 896h-768c-46.933 0-85.333-38.4-85.333-85.333v-597.333c0-46.933 38.4-85.333 85.333-85.333h768c46.933 0 85.333 38.4 85.333 85.333v597.333c0 46.933-38.4 85.333-85.333 85.333zM896 213.333h-768v128h768v-128z" />
|
<glyph unicode="" glyph-name="toggle-filmstrip" d="M896 896h-768c-46.933 0-85.333-38.4-85.333-85.333v-597.333c0-46.933 38.4-85.333 85.333-85.333h768c46.933 0 85.333 38.4 85.333 85.333v597.333c0 46.933-38.4 85.333-85.333 85.333zM896 213.333h-768v128h768v-128z" />
|
||||||
<glyph unicode="" glyph-name="feedback" d="M42.667 128h170.667v512h-170.667v-512zM981.333 597.333c0 46.933-38.4 85.333-85.333 85.333h-269.227l40.533 194.987 1.28 13.653c0 17.493-7.253 33.707-18.773 45.227l-45.227 44.8-280.747-281.173c-15.787-15.36-25.173-36.693-25.173-60.16v-426.667c0-46.933 38.4-85.333 85.333-85.333h384c35.413 0 65.707 21.333 78.507 52.053l128.853 300.8c3.84 9.813 5.973 20.053 5.973 31.147v81.493l-0.427 0.427 0.427 3.413z" />
|
<glyph unicode="" glyph-name="feedback" d="M42.667 128h170.667v512h-170.667v-512zM981.333 597.333c0 46.933-38.4 85.333-85.333 85.333h-269.227l40.533 194.987 1.28 13.653c0 17.493-7.253 33.707-18.773 45.227l-45.227 44.8-280.747-281.173c-15.787-15.36-25.173-36.693-25.173-60.16v-426.667c0-46.933 38.4-85.333 85.333-85.333h384c35.413 0 65.707 21.333 78.507 52.053l128.853 300.8c3.84 9.813 5.973 20.053 5.973 31.147v81.493l-0.427 0.427 0.427 3.413z" />
|
||||||
<glyph unicode="" glyph-name="raised-hand" d="M982 790v-620c0-94-78-170-172-170h-310c-46 0-90 18-122 50l-336 342s54 52 56 52c10 8 22 12 34 12 10 0 18-2 26-6 2 0 184-104 184-104v508c0 36 28 64 64 64s64-28 64-64v-300h42v406c0 36 28 64 64 64s64-28 64-64v-406h42v364c0 36 28 64 64 64s64-28 64-64v-364h44v236c0 36 28 64 64 64s64-28 64-64z" />
|
<glyph unicode="" glyph-name="raised-hand" d="M982 790v-620c0-94-78-170-172-170h-310c-46 0-90 18-122 50l-336 342s54 52 56 52c10 8 22 12 34 12 10 0 18-2 26-6 2 0 184-104 184-104v508c0 36 28 64 64 64s64-28 64-64v-300h42v406c0 36 28 64 64 64s64-28 64-64v-406h42v364c0 36 28 64 64 64s64-28 64-64v-364h44v236c0 36 28 64 64 64s64-28 64-64z" />
|
||||||
|
<glyph unicode="" glyph-name="menu-up" d="M512 682l256-256-60-60-196 196-196-196-60 60z" />
|
||||||
|
<glyph unicode="" glyph-name="menu-down" d="M708 658l60-60-256-256-256 256 60 60 196-196z" />
|
||||||
</font></defs></svg>
|
</font></defs></svg>
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 15 KiB |
BIN
fonts/jitsi.ttf
BIN
fonts/jitsi.ttf
Binary file not shown.
BIN
fonts/jitsi.woff
BIN
fonts/jitsi.woff
Binary file not shown.
|
@ -293,6 +293,58 @@
|
||||||
"setId": 2,
|
"setId": 2,
|
||||||
"iconIdx": 243
|
"iconIdx": 243
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"icon": {
|
||||||
|
"paths": [
|
||||||
|
"M512 342l256 256-60 60-196-196-196 196-60-60z"
|
||||||
|
],
|
||||||
|
"isMulticolor": false,
|
||||||
|
"isMulticolor2": false,
|
||||||
|
"tags": [
|
||||||
|
"expand_less"
|
||||||
|
],
|
||||||
|
"grid": 0,
|
||||||
|
"attrs": []
|
||||||
|
},
|
||||||
|
"attrs": [],
|
||||||
|
"properties": {
|
||||||
|
"id": 256,
|
||||||
|
"order": 106,
|
||||||
|
"ligatures": "expand_less",
|
||||||
|
"prevSize": 32,
|
||||||
|
"code": 59679,
|
||||||
|
"name": "menu-up"
|
||||||
|
},
|
||||||
|
"setIdx": 0,
|
||||||
|
"setId": 2,
|
||||||
|
"iconIdx": 257
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"icon": {
|
||||||
|
"paths": [
|
||||||
|
"M708 366l60 60-256 256-256-256 60-60 196 196z"
|
||||||
|
],
|
||||||
|
"isMulticolor": false,
|
||||||
|
"isMulticolor2": false,
|
||||||
|
"tags": [
|
||||||
|
"expand_more"
|
||||||
|
],
|
||||||
|
"grid": 0,
|
||||||
|
"attrs": []
|
||||||
|
},
|
||||||
|
"attrs": [],
|
||||||
|
"properties": {
|
||||||
|
"id": 257,
|
||||||
|
"order": 107,
|
||||||
|
"ligatures": "expand_more",
|
||||||
|
"prevSize": 32,
|
||||||
|
"code": 59680,
|
||||||
|
"name": "menu-down"
|
||||||
|
},
|
||||||
|
"setIdx": 0,
|
||||||
|
"setId": 2,
|
||||||
|
"iconIdx": 258
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"icon": {
|
"icon": {
|
||||||
"paths": [
|
"paths": [
|
||||||
|
|
48
index.html
48
index.html
|
@ -120,28 +120,32 @@
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</span>
|
</span>
|
||||||
<a class="button icon-contactList" id="toolbar_contact_list" data-container="body" data-toggle="popover" data-placement="right" shortcut="contactlistpopover" data-i18n="[content]bottomtoolbar.contactlist" content="Open / close contact list">
|
<a class="button icon-contactList" id="toolbar_contact_list" shortcut="contactlistpopover">
|
||||||
<span id="numberOfParticipants"></span>
|
<span class="badge-round">
|
||||||
|
<span id="numberOfParticipants"></span>
|
||||||
|
</span>
|
||||||
</a>
|
</a>
|
||||||
<!--a class="button icon-link" id="toolbar_button_link" data-container="body" data-toggle="popover" data-placement="right" data-i18n="[content]toolbar.invite" content="Invite others"></a-->
|
<!--a class="button icon-link" id="toolbar_button_link"></a-->
|
||||||
<a class="button icon-chat" id="toolbar_button_chat" data-container="body" data-toggle="popover" shortcut="toggleChatPopover" data-placement="right" data-i18n="[content]toolbar.chat" content="Open / close chat">
|
<a class="button icon-chat" id="toolbar_button_chat" shortcut="toggleChatPopover">
|
||||||
<span id="unreadMessages"></span>
|
<span class="badge-round">
|
||||||
|
<span id="unreadMessages"></span>
|
||||||
|
</span>
|
||||||
</a>
|
</a>
|
||||||
<a class="button" id="toolbar_button_record" data-container="body" data-toggle="popover" data-placement="right" style="display: none"></a>
|
<a class="button" id="toolbar_button_record" style="display: none"></a>
|
||||||
<a class="button icon-security" id="toolbar_button_security" data-container="body" data-toggle="popover" data-placement="right" data-i18n="[content]toolbar.lock" content="Lock / unlock room"></a>
|
<a class="button icon-security" id="toolbar_button_security"></a>
|
||||||
<a class="button icon-share-doc" id="toolbar_button_etherpad" data-container="body" data-toggle="popover" data-placement="right" content="Shared document" data-i18n="[content]toolbar.etherpad"></a>
|
<a class="button icon-share-doc" id="toolbar_button_etherpad"></a>
|
||||||
<a class="button icon-shared-video" id="toolbar_button_sharedvideo" data-container="body" data-toggle="popover" data-placement="right" content="Share a YouTube video" data-i18n="[content]toolbar.sharedvideo" style="display: none">
|
<a class="button icon-shared-video" id="toolbar_button_sharedvideo" style="display: none">
|
||||||
<ul id="sharedVideoMutedPopup" class="loginmenu extendedToolbarPopup">
|
<ul id="sharedVideoMutedPopup" class="loginmenu extendedToolbarPopup">
|
||||||
<li data-i18n="[html]toolbar.sharedVideoMutedPopup"></li>
|
<li data-i18n="[html]toolbar.sharedVideoMutedPopup"></li>
|
||||||
</ul>
|
</ul>
|
||||||
</a>
|
</a>
|
||||||
<a class="button icon-telephone" id="toolbar_button_sip" data-container="body" data-toggle="popover" data-placement="right" content="Call SIP number" data-i18n="[content]toolbar.sip" style="display: none"></a>
|
<a class="button icon-telephone" id="toolbar_button_sip" style="display: none"></a>
|
||||||
<a class="button icon-dialpad" id="toolbar_button_dialpad" data-container="body" data-toggle="popover" data-placement="right" content="Open dialpad" data-i18n="[content]toolbar.dialpad" style="display: none"></a>
|
<a class="button icon-dialpad" id="toolbar_button_dialpad" style="display: none"></a>
|
||||||
<a class="button icon-settings" id="toolbar_button_settings" data-container="body" data-toggle="popover" data-placement="right" content="Settings" data-i18n="[content]toolbar.Settings"></a>
|
<a class="button icon-settings" id="toolbar_button_settings"></a>
|
||||||
<a class="button icon-raised-hand" id="toolbar_button_raisehand" data-container="body" data-toggle="popover" data-placement="right" data-i18n="[content]toolbar.raiseHand" content="Raise Hand" shortcut="raiseHandPopover"></a>
|
<a class="button icon-raised-hand" id="toolbar_button_raisehand" shortcut="raiseHandPopover"></a>
|
||||||
<a class="button icon-full-screen" id="toolbar_button_fullScreen" data-container="body" data-toggle="popover" data-placement="right" shortcut="toggleFullscreenPopover" data-i18n="[content]toolbar.fullscreen" content="Enter / Exit Full Screen"></a>
|
<a class="button icon-full-screen" id="toolbar_button_fullScreen" shortcut="toggleFullscreenPopover"></a>
|
||||||
<a class="button icon-toggle-filmstrip" id="toolbar_film_strip" data-container="body" data-toggle="popover" shortcut="filmstripPopover" data-placement="right" data-i18n="[content]toolbar.filmstrip" content="Show / hide videos"></a>
|
<a class="button icon-toggle-filmstrip" id="toolbar_film_strip" data-container="body" shortcut="filmstripPopover"></a>
|
||||||
<a class="button icon-feedback" id="feedbackButton" data-container="body" data-toggle="popover" data-placement="right" data-i18n="[content]feedback"></a>
|
<a class="button icon-feedback" id="feedbackButton"></a>
|
||||||
<div id="sideToolbarContainer">
|
<div id="sideToolbarContainer">
|
||||||
<div id="profile_container" class="sideToolbarContainer__inner">
|
<div id="profile_container" class="sideToolbarContainer__inner">
|
||||||
<div class="title" data-i18n="profile.title"></div>
|
<div class="title" data-i18n="profile.title"></div>
|
||||||
|
@ -208,13 +212,11 @@
|
||||||
<input type="checkbox" id="followMeCheckBox">
|
<input type="checkbox" id="followMeCheckBox">
|
||||||
<label class="followMeLabel" for="followMeCheckBox" data-i18n="settings.followMe"></label>
|
<label class="followMeLabel" for="followMeCheckBox" data-i18n="settings.followMe"></label>
|
||||||
</div>
|
</div>
|
||||||
<a id="downloadlog" data-container="body" data-toggle="popover" data-placement="right" data-i18n="[data-content]downloadlogs" ><i class="icon-download"></i></a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="videospace">
|
<div id="videospace">
|
||||||
<div id="largeVideoContainer" class="videocontainer">
|
<div id="largeVideoContainer" class="videocontainer">
|
||||||
<div id="presentation"></div>
|
|
||||||
<div id="sharedVideo"><div id="sharedVideoIFrame"></div></div>
|
<div id="sharedVideo"><div id="sharedVideoIFrame"></div></div>
|
||||||
<div id="etherpad"></div>
|
<div id="etherpad"></div>
|
||||||
<a target="_new"><div class="watermark leftwatermark"></div></a>
|
<a target="_new"><div class="watermark leftwatermark"></div></a>
|
||||||
|
@ -223,13 +225,14 @@
|
||||||
<span data-i18n="poweredby"></span> jitsi.org
|
<span data-i18n="poweredby"></span> jitsi.org
|
||||||
</a>
|
</a>
|
||||||
<div id="dominantSpeaker">
|
<div id="dominantSpeaker">
|
||||||
|
<div class="dynamic-shadow"></div>
|
||||||
<img id="dominantSpeakerAvatar" src=""/>
|
<img id="dominantSpeakerAvatar" src=""/>
|
||||||
<canvas id="dominantSpeakerAudioLevel"></canvas>
|
|
||||||
</div>
|
</div>
|
||||||
|
<span id="remoteConnectionMessage"></span>
|
||||||
<div id="largeVideoWrapper">
|
<div id="largeVideoWrapper">
|
||||||
<video id="largeVideo" muted="true" autoplay></video>
|
<video id="largeVideo" muted="true" autoplay></video>
|
||||||
</div>
|
</div>
|
||||||
<span id="videoConnectionMessage"></span>
|
<span id="localConnectionMessage"></span>
|
||||||
<span id="videoResolutionLabel">HD</span>
|
<span id="videoResolutionLabel">HD</span>
|
||||||
<span id="recordingLabel" class="centeredVideoLabel">
|
<span id="recordingLabel" class="centeredVideoLabel">
|
||||||
<span id="recordingLabelText"></span>
|
<span id="recordingLabelText"></span>
|
||||||
|
@ -238,12 +241,12 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="remoteVideos">
|
<div id="remoteVideos">
|
||||||
<span id="localVideoContainer" class="videocontainer">
|
<span id="localVideoContainer" class="videocontainer videocontainer_small">
|
||||||
<span id="localVideoWrapper">
|
<span id="localVideoWrapper">
|
||||||
<!--<video id="localVideo" autoplay muted></video> - is now per stream generated -->
|
<!--<video id="localVideo" autoplay muted></video> - is now per stream generated -->
|
||||||
</span>
|
</span>
|
||||||
<audio id="localAudio" autoplay muted></audio>
|
<audio id="localAudio" autoplay muted></audio>
|
||||||
<span class="focusindicator"></span>
|
<div class="videocontainer__toolbar"></div>
|
||||||
</span>
|
</span>
|
||||||
<audio id="userJoined" src="sounds/joined.wav" preload="auto"></audio>
|
<audio id="userJoined" src="sounds/joined.wav" preload="auto"></audio>
|
||||||
<audio id="userLeft" src="sounds/left.wav" preload="auto"></audio>
|
<audio id="userLeft" src="sounds/left.wav" preload="auto"></audio>
|
||||||
|
@ -257,5 +260,6 @@
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="aui-feedback-dialog" class="dialog feedback aui-layer aui-dialog2 aui-dialog2-medium" style="display: none;"></div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -34,5 +34,14 @@ var interfaceConfig = {
|
||||||
filmStripOnly: false,
|
filmStripOnly: false,
|
||||||
RANDOM_AVATAR_URL_PREFIX: false,
|
RANDOM_AVATAR_URL_PREFIX: false,
|
||||||
RANDOM_AVATAR_URL_SUFFIX: false,
|
RANDOM_AVATAR_URL_SUFFIX: false,
|
||||||
FILM_STRIP_MAX_HEIGHT: 120
|
FILM_STRIP_MAX_HEIGHT: 120,
|
||||||
|
LOCAL_THUMBNAIL_RATIO_WIDTH: 16,
|
||||||
|
LOCAL_THUMBNAIL_RATIO_HEIGHT: 9,
|
||||||
|
REMOTE_THUMBNAIL_RATIO_WIDTH: 1,
|
||||||
|
REMOTE_THUMBNAIL_RATIO_HEIGHT: 1,
|
||||||
|
// Enables feedback star animation.
|
||||||
|
ENABLE_FEEDBACK_ANIMATION: false,
|
||||||
|
DISABLE_FOCUS_INDICATOR: false,
|
||||||
|
AUDIO_LEVEL_PRIMARY_COLOR: "rgba(255,255,255,0.7)",
|
||||||
|
AUDIO_LEVEL_SECONDARY_COLOR: "rgba(255,255,255,0.4)"
|
||||||
};
|
};
|
|
@ -7,7 +7,9 @@
|
||||||
"hy": "Armenisch",
|
"hy": "Armenisch",
|
||||||
"it": "Italienisch",
|
"it": "Italienisch",
|
||||||
"oc": "Okzitanisch",
|
"oc": "Okzitanisch",
|
||||||
|
"pl": "Polnisch",
|
||||||
"ptBR": "Portugiesisch (Brasilien)",
|
"ptBR": "Portugiesisch (Brasilien)",
|
||||||
|
"ru": "Russisch",
|
||||||
"sk": "Slowakisch",
|
"sk": "Slowakisch",
|
||||||
"sl": "Slowenisch",
|
"sl": "Slowenisch",
|
||||||
"sv": "Schwedisch",
|
"sv": "Schwedisch",
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"en": "Angielski",
|
||||||
|
"bg": "Bułgarski",
|
||||||
|
"de": "Niemiecki",
|
||||||
|
"es": "Hiszpański",
|
||||||
|
"fr": "Francuski",
|
||||||
|
"hy": "Ormiański",
|
||||||
|
"it": "Włoski",
|
||||||
|
"oc": "Prowansalski",
|
||||||
|
"ptBR": "portugalski (brazylijski)",
|
||||||
|
"sk": "Słowacki",
|
||||||
|
"sl": "Słoweński",
|
||||||
|
"sv": "Szwedzki",
|
||||||
|
"tr": "Turecki"
|
||||||
|
}
|
|
@ -7,7 +7,9 @@
|
||||||
"hy": "Armênio",
|
"hy": "Armênio",
|
||||||
"it": "Italiano",
|
"it": "Italiano",
|
||||||
"oc": "Provençal",
|
"oc": "Provençal",
|
||||||
|
"pl": "Polonês",
|
||||||
"ptBR": "Português (Brasil)",
|
"ptBR": "Português (Brasil)",
|
||||||
|
"ru": "Russo",
|
||||||
"sk": "Eslovaco",
|
"sk": "Eslovaco",
|
||||||
"sl": "Esloveno",
|
"sl": "Esloveno",
|
||||||
"sv": "Sueco",
|
"sv": "Sueco",
|
||||||
|
|
|
@ -8,7 +8,9 @@
|
||||||
"hy": "Armenian",
|
"hy": "Armenian",
|
||||||
"it": "Italian",
|
"it": "Italian",
|
||||||
"oc": "Occitan",
|
"oc": "Occitan",
|
||||||
|
"pl": "Polish",
|
||||||
"ptBR": "Portuguese (Brazil)",
|
"ptBR": "Portuguese (Brazil)",
|
||||||
|
"ru": "Russian",
|
||||||
"sk": "Slovak",
|
"sk": "Slovak",
|
||||||
"sl": "Slovenian",
|
"sl": "Slovenian",
|
||||||
"sv": "Swedish",
|
"sv": "Swedish",
|
||||||
|
|
|
@ -1,17 +1,15 @@
|
||||||
{
|
{
|
||||||
"contactlist": "Kontaktliste",
|
"contactlist": "Im Gespräch",
|
||||||
"connectionsettings": "Verbindungseinstellungen",
|
"connectionsettings": "Verbindungseinstellungen",
|
||||||
"poweredby": "Betrieben von",
|
"poweredby": "Betrieben von",
|
||||||
"downloadlogs": "Log herunterladen",
|
|
||||||
"feedback": "Wir freuen uns auf Ihr Feedback!",
|
"feedback": "Wir freuen uns auf Ihr Feedback!",
|
||||||
"roomUrlDefaultMsg": "Die Konferenz wird erstellt...",
|
"roomUrlDefaultMsg": "Die Konferenz wird erstellt...",
|
||||||
"participant": "Teilnehmer",
|
|
||||||
"me": "ich",
|
"me": "ich",
|
||||||
"speaker": "Sprecher",
|
"speaker": "Sprecher",
|
||||||
"raisedHand": "Möchte sprechen",
|
"raisedHand": "Möchte sprechen",
|
||||||
"defaultNickname": "Bsp: Heidi Blau",
|
"defaultNickname": "Bsp: Heidi Blau",
|
||||||
"defaultLink": "Bsp.: __url__",
|
"defaultLink": "Bsp.: __url__",
|
||||||
"calling": "Rufe __name__ an...",
|
"callingName": "__name__",
|
||||||
"userMedia": {
|
"userMedia": {
|
||||||
"react-nativeGrantPermissions": "Bitte Berechtigungen zur Verwendung der Kamera und des Mikrofons durch anwählen von <b><i>Erlauben</i></b>",
|
"react-nativeGrantPermissions": "Bitte Berechtigungen zur Verwendung der Kamera und des Mikrofons durch anwählen von <b><i>Erlauben</i></b>",
|
||||||
"chromeGrantPermissions": "Bitte Berechtigungen zur Verwendung der Kamera und des Mikrofons durch anwählen von <b><i>Erlauben</i></b>",
|
"chromeGrantPermissions": "Bitte Berechtigungen zur Verwendung der Kamera und des Mikrofons durch anwählen von <b><i>Erlauben</i></b>",
|
||||||
|
@ -27,7 +25,7 @@
|
||||||
"raiseHand": "Heben Sie Ihre Hand.",
|
"raiseHand": "Heben Sie Ihre Hand.",
|
||||||
"pushToTalk": "Drücken um zu sprechen.",
|
"pushToTalk": "Drücken um zu sprechen.",
|
||||||
"toggleScreensharing": "Zwischen Kamera und Bildschirmfreigabe wechseln.",
|
"toggleScreensharing": "Zwischen Kamera und Bildschirmfreigabe wechseln.",
|
||||||
"toggleFilmstrip": "Videovorschau anzeigen oder verstecken.",
|
"toggleFilmstrip": "Videos anzeigen oder verbergen.",
|
||||||
"toggleShortcuts": "Hilfe-Menu anzeigen oder verdecken.",
|
"toggleShortcuts": "Hilfe-Menu anzeigen oder verdecken.",
|
||||||
"focusLocal": "Lokales Video fokussieren.",
|
"focusLocal": "Lokales Video fokussieren.",
|
||||||
"focusRemote": "Andere Videos fokussieren.",
|
"focusRemote": "Andere Videos fokussieren.",
|
||||||
|
@ -37,7 +35,7 @@
|
||||||
},
|
},
|
||||||
"welcomepage": {
|
"welcomepage": {
|
||||||
"go": "Los",
|
"go": "Los",
|
||||||
"roomname": "Raumnamen eingeben",
|
"roomname": "Konferenzname eingeben",
|
||||||
"disable": "Diesen Hinweis nicht mehr anzeigen",
|
"disable": "Diesen Hinweis nicht mehr anzeigen",
|
||||||
"feature1": {
|
"feature1": {
|
||||||
"title": "Einfach zu benutzen",
|
"title": "Einfach zu benutzen",
|
||||||
|
@ -49,7 +47,7 @@
|
||||||
},
|
},
|
||||||
"feature3": {
|
"feature3": {
|
||||||
"title": "Open Source",
|
"title": "Open Source",
|
||||||
"content": "__app__ steht unter der Apache Lizenz. Es steht ihnen frei __app__ gemäß dieser Lizenz herunterzuladen, zu verändern oder zu verbreiten."
|
"content": "__app__ steht unter der Apache Lizenz. Es steht ihnen frei __app__ gemäss dieser Lizenz herunterzuladen, zu verändern oder zu verbreiten."
|
||||||
},
|
},
|
||||||
"feature4": {
|
"feature4": {
|
||||||
"title": "Unbegrenzte Anzahl Benutzer",
|
"title": "Unbegrenzte Anzahl Benutzer",
|
||||||
|
@ -76,16 +74,16 @@
|
||||||
"mute": "Stummschaltung aktivieren / deaktivieren",
|
"mute": "Stummschaltung aktivieren / deaktivieren",
|
||||||
"videomute": "Kamera starten / stoppen",
|
"videomute": "Kamera starten / stoppen",
|
||||||
"authenticate": "Anmelden",
|
"authenticate": "Anmelden",
|
||||||
"lock": "Raum schützen / Schutz aufheben",
|
"lock": "Konferenz schützen / Schutz aufheben",
|
||||||
"invite": "Andere einladen",
|
"invite": "Andere einladen",
|
||||||
"chat": "Chat öffnen / schließen",
|
"chat": "Chat öffnen / schliessen",
|
||||||
"etherpad": "Geteiltes Dokument",
|
"etherpad": "Dokument teilen",
|
||||||
"sharedvideo": "Ein YouTube-Video teilen",
|
"sharedvideo": "YouTube-Video teilen",
|
||||||
"sharescreen": "Bildschirm freigeben",
|
"sharescreen": "Bildschirm freigeben",
|
||||||
"fullscreen": "Vollbildmodus aktivieren / deaktivieren",
|
"fullscreen": "Vollbildmodus aktivieren / deaktivieren",
|
||||||
"sip": "SIP Nummer anrufen",
|
"sip": "SIP Nummer anrufen",
|
||||||
"Settings": "Einstellungen",
|
"Settings": "Einstellungen",
|
||||||
"hangup": "Auflegen",
|
"hangup": "Konferenz verlassen",
|
||||||
"login": "Anmelden",
|
"login": "Anmelden",
|
||||||
"logout": "Abmelden",
|
"logout": "Abmelden",
|
||||||
"dialpad": "Tastenblock anzeigen",
|
"dialpad": "Tastenblock anzeigen",
|
||||||
|
@ -93,17 +91,19 @@
|
||||||
"micMutedPopup": "Ihr Mikrofon wurde stumm geschaltet damit das<br/>geteilte Video genossen werden kann.",
|
"micMutedPopup": "Ihr Mikrofon wurde stumm geschaltet damit das<br/>geteilte Video genossen werden kann.",
|
||||||
"unableToUnmutePopup": "Die Stummschaltung kann nicht aufgehoben werden während das geteilte Video abgespielt wird.",
|
"unableToUnmutePopup": "Die Stummschaltung kann nicht aufgehoben werden während das geteilte Video abgespielt wird.",
|
||||||
"cameraDisabled": "Keine Kamera verfügbar",
|
"cameraDisabled": "Keine Kamera verfügbar",
|
||||||
"micDisabled": "Kein Mikrofon verfügbar"
|
"micDisabled": "Kein Mikrofon verfügbar",
|
||||||
|
"filmstrip": "Videos anzeigen / verbergen",
|
||||||
|
"raiseHand": "Hand erheben um zu sprechen"
|
||||||
},
|
},
|
||||||
"bottomtoolbar": {
|
"bottomtoolbar": {
|
||||||
"chat": "Chat öffnen / schließen",
|
"chat": "Chat öffnen / schliessen",
|
||||||
"filmstrip": "Videovorschau anzeigen / verstecken",
|
"filmstrip": "Videos anzeigen / verbergen",
|
||||||
"contactlist": "Kontaktliste öffnen / schließen"
|
"contactlist": "Kontaktliste öffnen / schliessen"
|
||||||
},
|
},
|
||||||
"chat": {
|
"chat": {
|
||||||
"nickname": {
|
"nickname": {
|
||||||
"title": "Nickname im Eingabefeld eingeben",
|
"title": "Name eingeben",
|
||||||
"popover": "Einen Namen auswählen"
|
"popover": "Name"
|
||||||
},
|
},
|
||||||
"messagebox": "Text eingeben..."
|
"messagebox": "Text eingeben..."
|
||||||
},
|
},
|
||||||
|
@ -111,20 +111,29 @@
|
||||||
"title": "Einstellungen",
|
"title": "Einstellungen",
|
||||||
"update": "Aktualisieren",
|
"update": "Aktualisieren",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"startAudioMuted": "Stumm beitreten",
|
"startAudioMuted": "Alle Teilnehmer treten stumm geschaltet bei",
|
||||||
"startVideoMuted": "Ohne Video beitreten",
|
"startVideoMuted": "Alle Teilnehmer treten ohne Video bei",
|
||||||
"selectCamera": "Kamera auswählen",
|
"selectCamera": "Kamera",
|
||||||
"selectMic": "Mikrofon auswählen",
|
"selectMic": "Mikrofon",
|
||||||
"selectAudioOutput": "Audio-Ausgabe auswählen",
|
"selectAudioOutput": "Audioausgabe",
|
||||||
"followMe": "Follow-me aktivieren",
|
"followMe": "Follow-me für alle Teilnehmer",
|
||||||
"noDevice": "Kein",
|
"noDevice": "Kein",
|
||||||
"noPermission": "Keine Berechtigung um das Gerät zu verwenden",
|
"noPermission": "Keine Berechtigung um das Gerät zu verwenden",
|
||||||
"avatarUrl": "Avatar URL"
|
"cameraAndMic": "Kamera und Mikrofon",
|
||||||
|
"moderator": "MODERATOR",
|
||||||
|
"password": "PASSWORT SETZEN",
|
||||||
|
"audioVideo": "AUDIO UND VIDEO",
|
||||||
|
"setPasswordLabel": "Konferenz mit einem Passwort schützen."
|
||||||
|
},
|
||||||
|
"profile": {
|
||||||
|
"title": "PROFIL",
|
||||||
|
"setDisplayNameLabel": "Anzeigename festlegen",
|
||||||
|
"setEmailLabel": "E-Mail Adresse für Gravatar"
|
||||||
},
|
},
|
||||||
"videothumbnail": {
|
"videothumbnail": {
|
||||||
"editnickname": "Klicken, um den Anzeigenamen zu bearbeiten",
|
"editnickname": "Klicken, um den Anzeigenamen zu bearbeiten",
|
||||||
"moderator": "Besitzer dieser Konferenz",
|
"moderator": "Besitzer dieser Konferenz",
|
||||||
"videomute": "Teilnehmer hat die Kamera pausiert.",
|
"videomute": "Teilnehmer hat die Kamera pausiert",
|
||||||
"mute": "Teilnehmer ist stumm geschaltet",
|
"mute": "Teilnehmer ist stumm geschaltet",
|
||||||
"kick": "Hinauswerfen",
|
"kick": "Hinauswerfen",
|
||||||
"muted": "Stummgeschaltet",
|
"muted": "Stummgeschaltet",
|
||||||
|
@ -172,6 +181,7 @@
|
||||||
"connectError": "Oh! Es hat etwas nicht geklappt und der Konferenz konnte nicht beigetreten werden.",
|
"connectError": "Oh! Es hat etwas nicht geklappt und der Konferenz konnte nicht beigetreten werden.",
|
||||||
"connectErrorWithMsg": "Oh! Es hat etwas nicht geklappt und der Konferenz konnte nicht beigetreten werden: __msg__",
|
"connectErrorWithMsg": "Oh! Es hat etwas nicht geklappt und der Konferenz konnte nicht beigetreten werden: __msg__",
|
||||||
"connecting": "Verbindung wird hergestellt",
|
"connecting": "Verbindung wird hergestellt",
|
||||||
|
"copy": "Kopieren",
|
||||||
"error": "Fehler",
|
"error": "Fehler",
|
||||||
"detectext": "Fehler bei der Erkennung der Bildschirmfreigabeerweiterung.",
|
"detectext": "Fehler bei der Erkennung der Bildschirmfreigabeerweiterung.",
|
||||||
"failtoinstall": "Die Bildschirmfreigabeerweiterung konnte nicht installiert werden.",
|
"failtoinstall": "Die Bildschirmfreigabeerweiterung konnte nicht installiert werden.",
|
||||||
|
@ -183,8 +193,8 @@
|
||||||
"lockMessage": "Die Konferenz konnte nicht gesperrt werden.",
|
"lockMessage": "Die Konferenz konnte nicht gesperrt werden.",
|
||||||
"warning": "Warnung",
|
"warning": "Warnung",
|
||||||
"passwordNotSupported": "Passwörter für Räume werden nicht unterstützt.",
|
"passwordNotSupported": "Passwörter für Räume werden nicht unterstützt.",
|
||||||
"sorry": "Entschuldigung",
|
"internalErrorTitle": "Interner Fehler",
|
||||||
"internalError": "Interner Anwendungsfehler [setRemoteDescription]",
|
"internalError": "Ups! Es ist etwas schiefgegangen. Der Fehler [setRemoteDescription] ist aufgetreten.",
|
||||||
"unableToSwitch": "Der Videodatenstrom kann nicht gewechselt werden.",
|
"unableToSwitch": "Der Videodatenstrom kann nicht gewechselt werden.",
|
||||||
"SLDFailure": "Oh! Die Stummschaltung konnte nicht aktiviert werden. (SLD Fehler)",
|
"SLDFailure": "Oh! Die Stummschaltung konnte nicht aktiviert werden. (SLD Fehler)",
|
||||||
"SRDFailure": "Oh! Das Video konnte nicht gestoppt werden. (SRD Fehler)",
|
"SRDFailure": "Oh! Das Video konnte nicht gestoppt werden. (SRD Fehler)",
|
||||||
|
@ -206,7 +216,7 @@
|
||||||
"logoutTitle": "Abmelden",
|
"logoutTitle": "Abmelden",
|
||||||
"logoutQuestion": "Sind Sie sicher, dass Sie sich abmelden und die Konferenz verlassen möchten?",
|
"logoutQuestion": "Sind Sie sicher, dass Sie sich abmelden und die Konferenz verlassen möchten?",
|
||||||
"sessTerminated": "Sitzung beendet",
|
"sessTerminated": "Sitzung beendet",
|
||||||
"hungUp": "Anruf beendet",
|
"hungUp": "Konferenz beendet",
|
||||||
"joinAgain": "Erneut beitreten",
|
"joinAgain": "Erneut beitreten",
|
||||||
"Share": "Teilen",
|
"Share": "Teilen",
|
||||||
"Save": "Speichern",
|
"Save": "Speichern",
|
||||||
|
@ -215,27 +225,29 @@
|
||||||
"Dial": "Wählen",
|
"Dial": "Wählen",
|
||||||
"sipMsg": "Geben Sie eine SIP Nummer ein",
|
"sipMsg": "Geben Sie eine SIP Nummer ein",
|
||||||
"passwordCheck": "Sind Sie sicher, dass Sie das Passwort entfernen möchten?",
|
"passwordCheck": "Sind Sie sicher, dass Sie das Passwort entfernen möchten?",
|
||||||
"passwordMsg": "Passwort setzen, um den Raum zu schützen",
|
"passwordMsg": "Passwort setzen um die Konferenz zu schützen",
|
||||||
"Invite": "Einladen",
|
"shareLink": "Diesen Link kopieren und teilen",
|
||||||
"shareLink": "Teilen Sie diesen Link mit jedem den Sie einladen möchten",
|
|
||||||
"settings1": "Konferenz einrichten",
|
"settings1": "Konferenz einrichten",
|
||||||
"settings2": "Teilnehmer treten stummgeschaltet bei",
|
"settings2": "Teilnehmer treten stummgeschaltet bei",
|
||||||
"settings3": "Nickname erforderlich<br/><br/>Setzen Sie ein Passwort, um den Raum zu schützen:",
|
"settings3": "Name erforderlich<br/><br/>Setzen Sie ein Passwort, um die Konferenz zu schützen:",
|
||||||
"yourPassword": "Ihr Passwort",
|
"yourPassword": "Neues Passwort eingeben",
|
||||||
"Back": "Zurück",
|
"Back": "Zurück",
|
||||||
"serviceUnavailable": "Dienst nicht verfügbar",
|
"serviceUnavailable": "Dienst nicht verfügbar",
|
||||||
"gracefulShutdown": "Der Dienst steht momentan wegen Wartungsarbeiten nicht zur Verfügung. Bitte versuchen Sie es später noch einmal.",
|
"gracefulShutdown": "Der Dienst steht momentan wegen Wartungsarbeiten nicht zur Verfügung. Bitte versuchen Sie es später noch einmal.",
|
||||||
"Yes": "Ja",
|
"Yes": "Ja",
|
||||||
"reservationError": "Fehler im Reservationssystem",
|
"reservationError": "Fehler im Reservationssystem",
|
||||||
"reservationErrorMsg": "Fehler, Nummer: __code__, Nachricht: __msg__",
|
"reservationErrorMsg": "Fehler, Nummer: __code__, Nachricht: __msg__",
|
||||||
"password": "Passwort",
|
"password": "Passwort eingeben",
|
||||||
"userPassword": "Benutzerpasswort",
|
"userPassword": "Benutzerpasswort",
|
||||||
"token": "Token",
|
"token": "Token",
|
||||||
"tokenAuthFailed": "Anmeldung am XMPP-Server fehlgeschlagen: ungültiges Token",
|
"tokenAuthFailedTitle": "Authentifizierungsfehler",
|
||||||
|
"tokenAuthFailed": "Sie sind nicht berechtigt dieser Konferenz beizutreten.",
|
||||||
"displayNameRequired": "Geben Sie Ihren Anzeigenamen ein",
|
"displayNameRequired": "Geben Sie Ihren Anzeigenamen ein",
|
||||||
"extensionRequired": "Erweiterung erforderlich:",
|
"extensionRequired": "Erweiterung erforderlich:",
|
||||||
"firefoxExtensionPrompt": "Um die Bildschirmfreigabe nutzen zu können, muss eine Firefox-Erweiterung installiert werden. Bitte versuchen Sie es erneut nachdem die <a href='__url__'>Erweiterung installiert</a> wurde.",
|
"firefoxExtensionPrompt": "Um die Bildschirmfreigabe nutzen zu können, muss eine Firefox-Erweiterung installiert werden. Bitte versuchen Sie es erneut nachdem die <a href='__url__'>Erweiterung installiert</a> wurde.",
|
||||||
"feedbackQuestion": "Wie war der Anruf?",
|
"rateExperience": "Bitte bewerten Sie diese Konferenz.",
|
||||||
|
"feedbackHelp": "Ihr Feedback hilft uns die Qualität der Konferenzen zu verbessern.",
|
||||||
|
"feedbackQuestion": "Anmerkungen zur Konferenz.",
|
||||||
"thankYou": "Danke für die Verwendung von __appName__!",
|
"thankYou": "Danke für die Verwendung von __appName__!",
|
||||||
"sorryFeedback": "Tut uns leid. Möchten Sie uns mehr mitteilen?",
|
"sorryFeedback": "Tut uns leid. Möchten Sie uns mehr mitteilen?",
|
||||||
"liveStreaming": "Live-Streaming",
|
"liveStreaming": "Live-Streaming",
|
||||||
|
@ -253,12 +265,17 @@
|
||||||
"cameraUnsupportedResolutionError": "Die Kamera unterstützt die erforderliche Auflösung nicht.",
|
"cameraUnsupportedResolutionError": "Die Kamera unterstützt die erforderliche Auflösung nicht.",
|
||||||
"cameraUnknownError": "Die Kamera kann aus einem unbekannten Grund nicht verwendet werden.",
|
"cameraUnknownError": "Die Kamera kann aus einem unbekannten Grund nicht verwendet werden.",
|
||||||
"cameraPermissionDeniedError": "Die Berechtigung zur Verwendung der Kamera wurde nicht erteilt. Sie können trotzdem an der Konferenz teilnehmen, aber die anderen Teilnehmer können Sie nicht sehen. Verwenden Sie die Kamera-Schaltfläche in der Adressleiste um die Berechtigungen zu erteilen.",
|
"cameraPermissionDeniedError": "Die Berechtigung zur Verwendung der Kamera wurde nicht erteilt. Sie können trotzdem an der Konferenz teilnehmen, aber die anderen Teilnehmer können Sie nicht sehen. Verwenden Sie die Kamera-Schaltfläche in der Adressleiste um die Berechtigungen zu erteilen.",
|
||||||
"cameraNotFoundError": "Die Berechtigung zur Verwendung der Kamera wurde nicht erteilt. Sie können trotzdem an der Konferenz teilnehmen, aber die anderen Teilnehmer können Sie nicht sehen. Verwenden Sie die Kamera-Schaltfläche in der Adressleiste um die Berechtigungen zu erteilen.",
|
"cameraNotFoundError": "Kamera nicht gefunden.",
|
||||||
"cameraConstraintFailedError": "Ihre Kamera erfüllt die notwendigen Anforderungen nicht.",
|
"cameraConstraintFailedError": "Ihre Kamera erfüllt die notwendigen Anforderungen nicht.",
|
||||||
"micUnknownError": "Das Mikrofon kann aus einem unbekannten Grund nicht verwendet werden.",
|
"micUnknownError": "Das Mikrofon kann aus einem unbekannten Grund nicht verwendet werden.",
|
||||||
"micPermissionDeniedError": "Die Berechtigung zur Verwendung des Mikrofons wurde nicht erteilt. Sie können trotzdem an der Konferenz teilnehmen, aber die anderen Teilnehmer können Sie nicht hören. Verwenden Sie die Kamera-Schaltfläche in der Adressleiste um die Berechtigungen zu erteilen.",
|
"micPermissionDeniedError": "Die Berechtigung zur Verwendung des Mikrofons wurde nicht erteilt. Sie können trotzdem an der Konferenz teilnehmen, aber die anderen Teilnehmer können Sie nicht hören. Verwenden Sie die Kamera-Schaltfläche in der Adressleiste um die Berechtigungen zu erteilen.",
|
||||||
"micNotFoundError": "Das angeforderte Mikrofon konnte nicht gefunden werden.",
|
"micNotFoundError": "Mikrofon nicht gefunden.",
|
||||||
"micConstraintFailedError": "Ihr Mikrofon erfüllt die notwendigen Anforderungen nicht."
|
"micConstraintFailedError": "Ihr Mikrofon erfüllt die notwendigen Anforderungen nicht.",
|
||||||
|
"micNotSendingData": "Das Mikrofon kann nicht verwendet werden. Bitte wählen Sie ein anderes Mikrofon in den Einstellungen oder laden Sie die Konferenz neu.",
|
||||||
|
"cameraNotSendingData": "Die Kamera kann nicht verwendet werden. Bitte wählen Sie eine andere Kamera in den Einstellungen oder laden Sie die Konferenz neu.",
|
||||||
|
"goToStore": "Zum Store",
|
||||||
|
"externalInstallationTitle": "Erweiterung erforderlich",
|
||||||
|
"externalInstallationMsg": "Die Bildschirmfreigabeerweiterung muss installiert werden."
|
||||||
},
|
},
|
||||||
"\u0005dialog": {},
|
"\u0005dialog": {},
|
||||||
"email": {
|
"email": {
|
||||||
|
|
|
@ -0,0 +1,344 @@
|
||||||
|
{
|
||||||
|
"contactlist": "w trakcie rozmowy",
|
||||||
|
"connectionsettings": "ustawienia połączenia",
|
||||||
|
"poweredby": "Uruchomiono",
|
||||||
|
"feedback": "jaka jest twoja opinia ?",
|
||||||
|
"roomUrlDefaultMsg": "otwarto twoją konferencję",
|
||||||
|
"me": "to ja",
|
||||||
|
"speaker": "głośnik",
|
||||||
|
"raisedHand": "Chcesz się odezwać ?",
|
||||||
|
"defaultNickname": "np. Ziutek Kowalski",
|
||||||
|
"defaultLink": "np. _url_",
|
||||||
|
"callingName": "_nazwa_",
|
||||||
|
"userMedia": {
|
||||||
|
"react-nativeGrantPermissions": "",
|
||||||
|
"chromeGrantPermissions": "",
|
||||||
|
"androidGrantPermissions": "",
|
||||||
|
"firefoxGrantPermissions": "wyraź zgodę na użycie kamery i mikrofonu naciskając <b><i>Share Selected Device</i></b> przycisk",
|
||||||
|
"operaGrantPermissions": "wyraź zgodę na użycie kamery i mikrofonu naciskając <b><i>Allow</i></b> przycisk",
|
||||||
|
"iexplorerGrantPermissions": "",
|
||||||
|
"safariGrantPermissions": "wyraź zgodę na użycie kamery i mikrofonu naciskając <b><i>OK</i></b> przycisk",
|
||||||
|
"nwjsGrantPermissions": "wyraź zgodę na użycie kamery i mikrofonu"
|
||||||
|
},
|
||||||
|
"keyboardShortcuts": {
|
||||||
|
"keyboardShortcuts": "Skróty klawiaturowe:",
|
||||||
|
"raiseHand": "Unieś rękę.",
|
||||||
|
"pushToTalk": "naciśnij i mów",
|
||||||
|
"toggleScreensharing": "Przełączanie pomiędzy kamerą i wspóldzieleniem ekranu",
|
||||||
|
"toggleFilmstrip": "Pokaż lub ukryj klipy wideo",
|
||||||
|
"toggleShortcuts": "Pokaż lub ukryj pasek pomocy.",
|
||||||
|
"focusLocal": "Przełącz na lokalne wideo.",
|
||||||
|
"focusRemote": "Przełącz na któreś ze zdalnych wideo.",
|
||||||
|
"toggleChat": "Otwórz lub zamknij panel czat.",
|
||||||
|
"mute": "Wyłącz lub włącz mikrofon.",
|
||||||
|
"videoMute": "Start lub stop lokalne wideo."
|
||||||
|
},
|
||||||
|
"welcomepage": {
|
||||||
|
"go": "IDŹ",
|
||||||
|
"roomname": "Podaj nazwę sali konferencyjnej",
|
||||||
|
"disable": "nie pokazuj ponownie",
|
||||||
|
"feature1": {
|
||||||
|
"title": "użyj",
|
||||||
|
"content": "nie musisz nic pobierać. _app_ jest gotowa do użycia bezpośrednio w przeglądarce. Zaproś innych do udziału w konferencji podając adres URL"
|
||||||
|
},
|
||||||
|
"feature2": {
|
||||||
|
"title": "za mała przepustowość",
|
||||||
|
"content": "Dla konferencji video potrzeba nie więcej niż 128 kbit/sek. Konferencje dzielenia ekranu lub tylko audio są możliwe przy mniejszej przepustowości. "
|
||||||
|
},
|
||||||
|
"feature3": {
|
||||||
|
"title": "Open source",
|
||||||
|
"content": "_app_ oparta jest na Apache License. Możesz swobodnie pobierać ją, używać, modyfikować i dzielić się nią."
|
||||||
|
},
|
||||||
|
"feature4": {
|
||||||
|
"title": "Nieograniczona liczba użytkowników",
|
||||||
|
"content": "Liczba użytkowników czy uczestników konferencji nie jest ograniczona. Determinuje ją moc serwera i dostępna przepustowość lącza."
|
||||||
|
},
|
||||||
|
"feature5": {
|
||||||
|
"title": "Współdzielenie ekranu",
|
||||||
|
"content": "Z łatwością podzielisz się ekranem z innymi. _app_ jest idealnym narzędziem do prezentacji, nauczania i udzielania zdalnej pomocy technicznej."
|
||||||
|
},
|
||||||
|
"feature6": {
|
||||||
|
"title": "Sale bezpieczne.",
|
||||||
|
"content": "Potrzebujesz prywatności? _app_ sale konferencyjne mogą być zabezpieczone hasłami niedopuszczającymi niezaproszonych uczestników czy też osoby chcące zakłócić konferencję."
|
||||||
|
},
|
||||||
|
"feature7": {
|
||||||
|
"title": "Współdzielenie uwag.",
|
||||||
|
"content": "_app_ zawiera Etherpad, współdzielony edytor tekstu doskonały dla redakcji zespołowych artykułów czy komentarzy."
|
||||||
|
},
|
||||||
|
"feature8": {
|
||||||
|
"title": "Statystyki użycia.",
|
||||||
|
"content": "Analizuj uczestników konferencji z łatwościa integrując dane z Piwik i Google Analitics i innymi systemami monitorującymi."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"toolbar": {
|
||||||
|
"mute": "Wycisz / Pogłośnij",
|
||||||
|
"videomute": "Kamera start / stop ",
|
||||||
|
"authenticate": "Uwierzytelnianie",
|
||||||
|
"lock": "Zamknij / Otwórz salę",
|
||||||
|
"invite": "Zaproś innych",
|
||||||
|
"chat": "",
|
||||||
|
"etherpad": "Udostępniaj dokument",
|
||||||
|
"sharedvideo": "Udostępniaj wideo w Youtube",
|
||||||
|
"sharescreen": "Udostępnij ekran",
|
||||||
|
"fullscreen": "Otwórz / Zamknij pełny ekran",
|
||||||
|
"sip": "Wykręć numer SIP",
|
||||||
|
"Settings": "",
|
||||||
|
"hangup": "Rozłącz",
|
||||||
|
"login": "Zaloguj",
|
||||||
|
"logout": "",
|
||||||
|
"dialpad": "Wyświetl panel wybierania",
|
||||||
|
"sharedVideoMutedPopup": "Współdzielone wideo zostało wyciszone i <br/> możesz zacząć rozmawiać z innymi.",
|
||||||
|
"micMutedPopup": "Mikrofon został wyłączony i <br/> możesz spokojnie konsumować współdzielone wideo",
|
||||||
|
"unableToUnmutePopup": "Nie możesz pogłośnić audio podczas współużytkowania wideo",
|
||||||
|
"cameraDisabled": "Kamera nie jest dostępna",
|
||||||
|
"micDisabled": "Mikrofon nie jest dostępny",
|
||||||
|
"filmstrip": "",
|
||||||
|
"raiseHand": "Podnieś rękę chcąc zabrać głos"
|
||||||
|
},
|
||||||
|
"bottomtoolbar": {
|
||||||
|
"chat": "Otwórz / Zamknij Czat",
|
||||||
|
"filmstrip": "Pokaż / Ukryj klipy wideo",
|
||||||
|
"contactlist": "Otwórz / Zamknij spis kontaktów"
|
||||||
|
},
|
||||||
|
"chat": {
|
||||||
|
"nickname": {
|
||||||
|
"title": "Podaj swój nick poniżej",
|
||||||
|
"popover": "Wybierz swój nick"
|
||||||
|
},
|
||||||
|
"messagebox": "Umieść tekst...."
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"title": "Ustawienia",
|
||||||
|
"update": "Aktualizacja",
|
||||||
|
"name": "Nazwa",
|
||||||
|
"startAudioMuted": "Wszyscy się wyciszyli",
|
||||||
|
"startVideoMuted": "Wszyscy się ukryli",
|
||||||
|
"selectCamera": "Kamera",
|
||||||
|
"selectMic": "Mikrofon",
|
||||||
|
"selectAudioOutput": "Wyjście audio",
|
||||||
|
"followMe": "Wszyscy za mną",
|
||||||
|
"noDevice": "Brak",
|
||||||
|
"noPermission": "Nie ma zgody na użycie urządzenia",
|
||||||
|
"cameraAndMic": "Kamera i Mikrofon",
|
||||||
|
"moderator": "MODERATOR",
|
||||||
|
"password": "USTAW HASŁO",
|
||||||
|
"audioVideo": "AUDIO I WIDEO",
|
||||||
|
"setPasswordLabel": "Zamknij salę konferencyjną z hasłem"
|
||||||
|
},
|
||||||
|
"profile": {
|
||||||
|
"title": "PROFIL",
|
||||||
|
"setDisplayNameLabel": "Podaj swoją wyświetlaną nazwę",
|
||||||
|
"setEmailLabel": "Ustaw email swojego gravatara"
|
||||||
|
},
|
||||||
|
"videothumbnail": {
|
||||||
|
"editnickname": "Kliknij <br/>celem edycji swojej nazwy",
|
||||||
|
"moderator": "Gospodarz <br/>tej konferencji",
|
||||||
|
"videomute": "Uczestnik <br/>wyłączyl kamerę",
|
||||||
|
"mute": "Uczestnik ma wyciszone audio",
|
||||||
|
"kick": "Kick out",
|
||||||
|
"muted": "Wyciszony",
|
||||||
|
"domute": "Wyciszenie",
|
||||||
|
"flip": "Odwrócenie"
|
||||||
|
},
|
||||||
|
"connectionindicator": {
|
||||||
|
"bitrate": "Szybkość transmisji:",
|
||||||
|
"packetloss": "Strata pakietów:",
|
||||||
|
"resolution": "Rozdzielczość:",
|
||||||
|
"less": "Pokaż mniej",
|
||||||
|
"more": "Pokaż więcej",
|
||||||
|
"address": "Adres:",
|
||||||
|
"remoteport": "Zdalny port:Zdalne porty:",
|
||||||
|
"remoteport_plural_2": "",
|
||||||
|
"remoteport_plural_5": "",
|
||||||
|
"localport": "Lokalny port:Lokalne porty:",
|
||||||
|
"localport_plural_2": "",
|
||||||
|
"localport_plural_5": "",
|
||||||
|
"localaddress": "Lokalny adres:Lokalne Adresy:",
|
||||||
|
"localaddress_plural_2": "",
|
||||||
|
"localaddress_plural_5": "",
|
||||||
|
"remoteaddress": "Zdalny adres:Zdalne adresy:",
|
||||||
|
"remoteaddress_plural_2": "",
|
||||||
|
"remoteaddress_plural_5": "",
|
||||||
|
"transport": "Przekazywanie:",
|
||||||
|
"bandwidth": "Zakładana przepustowość:",
|
||||||
|
"na": "Po informację o połączeniu wróć gdy wystartuje konferencja"
|
||||||
|
},
|
||||||
|
"notify": {
|
||||||
|
"disconnected": "rozłączone",
|
||||||
|
"moderator": "Prawa moderatora przydzielone!",
|
||||||
|
"connected": "połączono",
|
||||||
|
"somebody": "Ktoś",
|
||||||
|
"me": "To ja",
|
||||||
|
"focus": "Fokus konferencji",
|
||||||
|
"focusFail": "_składnik_nie dostępny - zastosuj w _ms_sek",
|
||||||
|
"grantedTo": "Prawa moderatora przyznane _to_!",
|
||||||
|
"grantedToUnknown": "Prawa Moderatora przyznane $t(somebody)!",
|
||||||
|
"muted": "Masz wyciszony mikrofon",
|
||||||
|
"mutedTitle": "Jesteś wyciszony!",
|
||||||
|
"raisedHand": "Możesz mówić."
|
||||||
|
},
|
||||||
|
"dialog": {
|
||||||
|
"kickMessage": "Ocho! Zostałeś wyproszony z konferencji!",
|
||||||
|
"popupError": "Twoja przeglądarka blokuje wyskakujące okienka z tej witryny. Proszę, zmień w ustawieniach przeglądarki.",
|
||||||
|
"passwordError": "Ta konwersacja aktualnie jest zabezpieczona hasłem. Tylko gospodarz konferencji może zakładać hasło.",
|
||||||
|
"passwordError2": "Ta rozmowa nie jest zabezpieczona hasłem. Tylko gospodarz konferencji może ustanowić hasło zabezpieczające.",
|
||||||
|
"connectError": "Ocho! Cos poszło nie tak, nie można podłaczyć się do tej konferencji.",
|
||||||
|
"connectErrorWithMsg": "Ocho! Coś poszło nie tak i nie można podłączyć się do tej konferencji:_msg_",
|
||||||
|
"connecting": "",
|
||||||
|
"copy": "Kopiuj",
|
||||||
|
"error": "",
|
||||||
|
"detectext": "Błąd podczas rozpoznania rozszerzenia wspóldzielenia ekranu.",
|
||||||
|
"failtoinstall": "Instalacja współdzielenia ekranu nie powiodła się.",
|
||||||
|
"failedpermissions": "Brak akceptacji dla użycia kamery i mikrofonu",
|
||||||
|
"bridgeUnavailable": "Jitsi Videobridge aktualnie jest niedostępne. Proszę, spróbuj później!",
|
||||||
|
"jicofoUnavailable": "Jicofo jest aktualnie niedostępne. Proszę, spróbuj później!",
|
||||||
|
"maxUsersLimitReached": "Osiągnięto max liczbę uczestników konferencji. Proszę spróbuj później! ",
|
||||||
|
"lockTitle": "Nie powiodło się zabezpieczenie konferencji",
|
||||||
|
"lockMessage": "Zabezpieczenie konferencji nie powiodło się.",
|
||||||
|
"warning": "Uwaga",
|
||||||
|
"passwordNotSupported": "Hasła sali konferencyjnych są aktualnie niedostępne.",
|
||||||
|
"internalErrorTitle": "Błąd wewnętrzny",
|
||||||
|
"internalError": "Ocho! coś poszło nie tak. Wystąpił błąd: [setRemoteDescription]",
|
||||||
|
"unableToSwitch": "Nie można przełaczyć na strumień wideo",
|
||||||
|
"SLDFailure": "Ocho! Coś poszło nie tak i nie można wyciszyć! (SLD Failure)",
|
||||||
|
"SRDFailure": "Ocho! Coś poszło nie tak i nie można zatrzymać wideo! (SRD Failure)",
|
||||||
|
"oops": "Ups",
|
||||||
|
"defaultError": "Wystąpił jakiś błąd",
|
||||||
|
"passwordRequired": "Wymagane hasło",
|
||||||
|
"Ok": "Ok",
|
||||||
|
"Remove": "Usuń",
|
||||||
|
"shareVideoTitle": "Współdziel wideo",
|
||||||
|
"shareVideoLinkError": "Podaj proszę prawidłowy link youtube.",
|
||||||
|
"removeSharedVideoTitle": "Usuń wideo współdzielone",
|
||||||
|
"removeSharedVideoMsg": "Na pewno chcesz usunąć współdzielone wideo?",
|
||||||
|
"alreadySharedVideoMsg": "Inny uczestnik aktualnie współdzieli wideo. W tej konferencji tylko jedno wideo może być współdzielone.",
|
||||||
|
"WaitingForHost": "Oczekiwanie na komputer",
|
||||||
|
"WaitForHostMsg": "Konferencja <b>_room_</b> jeszcze nie wystartowała. Jeśli jesteś gospodarzem podaj dane autentykacji. Jeśli nie czekaj na gospodarza.",
|
||||||
|
"IamHost": "Jestem gospodarzem",
|
||||||
|
"Cancel": "Anuluj",
|
||||||
|
"retry": "Ponów",
|
||||||
|
"logoutTitle": "Wyloguj",
|
||||||
|
"logoutQuestion": "Na pewno chcesz się wylogować i zakończyć konferencję?",
|
||||||
|
"sessTerminated": "Sesja zakończona",
|
||||||
|
"hungUp": "Przerwałeś połączenie",
|
||||||
|
"joinAgain": "Ponownie przystąp",
|
||||||
|
"Share": "Współdziel",
|
||||||
|
"Save": "Zapisz",
|
||||||
|
"recording": "",
|
||||||
|
"recordingToken": "Proszę podać token nagrywania",
|
||||||
|
"Dial": "Dzwoń",
|
||||||
|
"sipMsg": "Podaj numer SIP",
|
||||||
|
"passwordCheck": "Czy na pewno chcesz usunąć swoje hasło ?",
|
||||||
|
"passwordMsg": "Podaj hasło aby zabezpieczyć salę konferencyjną",
|
||||||
|
"shareLink": "Skopiuj i udostępnij ten link",
|
||||||
|
"settings1": "Skonfiguruj swoją konferencję",
|
||||||
|
"settings2": "Wyciszenie współuczestników",
|
||||||
|
"settings3": "Wymagane nicki <br/><br/>Wprowadź hasło dla zabezpieczenia sali konferencyjnej:",
|
||||||
|
"yourPassword": "Proszę wprowadzić nowe hasło",
|
||||||
|
"Back": "Wstecz",
|
||||||
|
"serviceUnavailable": "Usługa jest niedostępna",
|
||||||
|
"gracefulShutdown": "Aktualnie serwis jest konserwowany. Prosze spróbować później.",
|
||||||
|
"Yes": "Tak",
|
||||||
|
"reservationError": "Błąd systemu rezerwacji",
|
||||||
|
"reservationErrorMsg": "Kod błędu: _code_, treść: _msg_",
|
||||||
|
"password": "Podaj hasło",
|
||||||
|
"userPassword": "hasło użytkownika",
|
||||||
|
"token": "token",
|
||||||
|
"tokenAuthFailedTitle": "Problem uwierzytelnienia",
|
||||||
|
"tokenAuthFailed": "Przepraszam, ale nie jesteś upoważniony do uczestnictwa w tym połączeniu",
|
||||||
|
"displayNameRequired": "Wprowadź swoją nazwę użytkownika",
|
||||||
|
"extensionRequired": "Wymagane jest rozszerzenie:",
|
||||||
|
"firefoxExtensionPrompt": "Potrzebujesz zainstalować rozszerzenie firefox aby móc współdzielić ekran. Spróbuj ponownie później <a href='__url__'>weź z</a>!",
|
||||||
|
"rateExperience": "Oceń proszę swoje doświadczenia z konferencji.",
|
||||||
|
"feedbackHelp": "Twoja opinia będzie pomocna w usprawnieniu naszego serwisu.",
|
||||||
|
"feedbackQuestion": "Powiedz nam o twoim połączeniu!",
|
||||||
|
"thankYou": "Dziękujemy Ci za używanie _appName_!",
|
||||||
|
"sorryFeedback": "Przykro nam to słyszeć. Czy możesz powiedzieć więcej na ten temat?",
|
||||||
|
"liveStreaming": "",
|
||||||
|
"streamKey": "Nazwa strumienia/klucz",
|
||||||
|
"startLiveStreaming": "Uruchom strumień live",
|
||||||
|
"stopStreamingWarning": "Czy jesteś pewny, że chcesz zatrzymać ten strumień live?",
|
||||||
|
"stopRecordingWarning": "Naprawdę chcesz zatrzymać nagrywanie?",
|
||||||
|
"stopLiveStreaming": "Zatrzymaj transmisję live",
|
||||||
|
"stopRecording": "Zatrzymaj nagrywanie",
|
||||||
|
"doNotShowWarningAgain": "Nie pokazuj tego ostrzeżenia ponownie",
|
||||||
|
"permissionDenied": "Brak uprawnień",
|
||||||
|
"screenSharingPermissionDeniedError": "Nie posiadasz uprawnień do współdzielenia ekranu.",
|
||||||
|
"micErrorPresent": "Wystąpił błąd w dostępie do mikrofonu.",
|
||||||
|
"cameraErrorPresent": "Wystąpił błąd w dostępie do twojej kamery.",
|
||||||
|
"cameraUnsupportedResolutionError": "Twoja kamera nie obsługuje wymaganej rozdzielczości.",
|
||||||
|
"cameraUnknownError": "Z nieznanej przyczyny nie można użyć kamery ",
|
||||||
|
"cameraPermissionDeniedError": "Nie udzieliłeś pozwolenia na użycie twojej kamery. Nadal możesz włączyć się do konferencji ale inni nie będą cię widzieli. Naciśnij przycisk kamera w pasku menu aby użyć właściwą kamerę. ",
|
||||||
|
"cameraNotFoundError": "Kamera nie znaleziona.",
|
||||||
|
"cameraConstraintFailedError": "Twoja kamera nie spełnia wymagań.",
|
||||||
|
"micUnknownError": "Z przyczyn nieznanych nie można użyć mikrofonu. ",
|
||||||
|
"micPermissionDeniedError": "Nie udzieliłeś pozwolenia na użycie twojego mikrofonu. Nadal możesz uczestniczyc w konferencji ale inni nie będą cię słyszeli. Użyj przycisku kamera aby to naprawić.",
|
||||||
|
"micNotFoundError": "Mikrofon nie jest odnaleziony.",
|
||||||
|
"micConstraintFailedError": "Twój mikrofon nie obsługuje wymaganych parametrów.",
|
||||||
|
"micNotSendingData": "Nie możemy mieć dostępu do twojego mikrofonu. Proszę, wskaż inne urządzenie lub przeładuj aplikację.",
|
||||||
|
"cameraNotSendingData": "Nie możemy mieć dostępu do twojej kamery. Sprawdź czy inna aplikacja nie używa twojej kamery, wybierz inne urządzenie lub ponownie uruchom aplikację.",
|
||||||
|
"goToStore": "Idź do sklepu",
|
||||||
|
"externalInstallationTitle": "Wymagane rozszerzenie",
|
||||||
|
"externalInstallationMsg": "Zainstaluj rozszerzenie naszego współdzielenia ekranu."
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"sharedKey": [
|
||||||
|
"Ta konferencja jest zabezpieczona hasłem. Aby się podłączyć proszę zastosuj następujący pin:",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"_sharedKey_",
|
||||||
|
"",
|
||||||
|
" "
|
||||||
|
],
|
||||||
|
"subject": "Zaproszenie do a_appName_(_conferenceName_)",
|
||||||
|
"body": [
|
||||||
|
"Witaj, I%27 zaprasza cię do udziału w konferencji_appName_.",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"Kliknij na poniższy link aby uczestniczyć w konferencji.",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"_roomUrl_",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"_sharedKeyTex_",
|
||||||
|
"Zauważ, że -appName_ możesz używać tylko przy pomocy _supportedBrowsers_.",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"Polączymy się błyskawicznie! "
|
||||||
|
],
|
||||||
|
"and": "i"
|
||||||
|
},
|
||||||
|
"connection": {
|
||||||
|
"ERROR": "Błąd",
|
||||||
|
"CONNECTING": "Nawiązywanie połączenia",
|
||||||
|
"RECONNECTING": "Wystąpił problem w sieci. Ponowienie połaczenia....",
|
||||||
|
"CONNFAIL": "Połączenie się nie powiodło",
|
||||||
|
"AUTHENTICATING": "Uwierzytelnianie",
|
||||||
|
"AUTHFAIL": "Uwierzytelnianie nie powiodło się",
|
||||||
|
"CONNECTED": "Połączono",
|
||||||
|
"DISCONNECTED": "Rozłączony",
|
||||||
|
"DISCONNECTING": "Rozłączanie",
|
||||||
|
"ATTACHED": "Załącznik"
|
||||||
|
},
|
||||||
|
"recording": {
|
||||||
|
"pending": "Nagrywanie oczekiwanie na uczestników konferencji.....",
|
||||||
|
"on": "Nagrywanie",
|
||||||
|
"off": "Nagrywanie zatrzymane",
|
||||||
|
"failedToStart": "Nagrywanie nie jest możliwe",
|
||||||
|
"buttonTooltip": "Nagrywanie start / stop",
|
||||||
|
"error": "Nagranie się nie powiodło. Proszę, spróbuj ponownie.",
|
||||||
|
"unavailable": "Serwis nagrywania jest aktualnie niedostępny. Proszę, spróbować później."
|
||||||
|
},
|
||||||
|
"liveStreaming": {
|
||||||
|
"pending": "Start strumieniowania live...",
|
||||||
|
"on": "Strumień live",
|
||||||
|
"off": "Strumieniowanie live zastopowane",
|
||||||
|
"unavailable": "Strumieniowanie live aktualnie jest niedostepne. Proszę spróbować później.",
|
||||||
|
"failedToStart": "Strumieniowanie live nie powiodło się",
|
||||||
|
"buttonTooltip": "Strumieniowanie live start / stop",
|
||||||
|
"streamIdRequired": "Proszę podaj id strumieniowania aby uruchomić live.",
|
||||||
|
"error": "Strumieniowanie live nie powiodło się. Spróbuj później.",
|
||||||
|
"busy": "Wszystkie nagrywarki są zajęte. Proszę, sprawdź ponownie później."
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,17 +1,15 @@
|
||||||
{
|
{
|
||||||
"contactlist": "LISTA DE CONTATO",
|
"contactlist": "Na chamada",
|
||||||
"connectionsettings": "Configurações de conexão",
|
"connectionsettings": "Configurações de conexão",
|
||||||
"poweredby": "distribuído por",
|
"poweredby": "distribuído por",
|
||||||
"downloadlogs": "Baixar registros",
|
|
||||||
"feedback": "Dê seus comentários",
|
"feedback": "Dê seus comentários",
|
||||||
"roomUrlDefaultMsg": "Sua conferência está sendo criado...",
|
"roomUrlDefaultMsg": "Sua conferência está sendo criado...",
|
||||||
"participant": "Participante",
|
|
||||||
"me": "eu",
|
"me": "eu",
|
||||||
"speaker": "Orador",
|
"speaker": "Orador",
|
||||||
"raisedHand": "Gostaria de falar",
|
"raisedHand": "Gostaria de falar",
|
||||||
"defaultNickname": "ex. João Pedro",
|
"defaultNickname": "ex. João Pedro",
|
||||||
"defaultLink": "i.e. __url__",
|
"defaultLink": "i.e. __url__",
|
||||||
"calling": "Chamando __name__ ...",
|
"callingName": "__name__",
|
||||||
"userMedia": {
|
"userMedia": {
|
||||||
"react-nativeGrantPermissions": "Dê as permissões para usar sua câmera e microfone pressionando o botão <b> <i>Permitir</i> </b>",
|
"react-nativeGrantPermissions": "Dê as permissões para usar sua câmera e microfone pressionando o botão <b> <i>Permitir</i> </b>",
|
||||||
"chromeGrantPermissions": "Dê as permissões para usar sua câmera e microfone pressionando o botão <b> <i>Permitir</i> </b>",
|
"chromeGrantPermissions": "Dê as permissões para usar sua câmera e microfone pressionando o botão <b> <i>Permitir</i> </b>",
|
||||||
|
@ -27,7 +25,7 @@
|
||||||
"raiseHand": "Erguer sua mão.",
|
"raiseHand": "Erguer sua mão.",
|
||||||
"pushToTalk": "Pressione para falar.",
|
"pushToTalk": "Pressione para falar.",
|
||||||
"toggleScreensharing": "Trocar entre câmera e compartilhamento de tela.",
|
"toggleScreensharing": "Trocar entre câmera e compartilhamento de tela.",
|
||||||
"toggleFilmstrip": "Mostrar ou ocultar a tira de filme.",
|
"toggleFilmstrip": "Mostrar ou ocultar os vídeos.",
|
||||||
"toggleShortcuts": "Mostrar ou ocultar este menu de ajuda.",
|
"toggleShortcuts": "Mostrar ou ocultar este menu de ajuda.",
|
||||||
"focusLocal": "Foco no vídeo local.",
|
"focusLocal": "Foco no vídeo local.",
|
||||||
"focusRemote": "Foco em um dos vídeos remotos.",
|
"focusRemote": "Foco em um dos vídeos remotos.",
|
||||||
|
@ -93,11 +91,13 @@
|
||||||
"micMutedPopup": "Seu microfone está mudo assim que você<br/>pode curtir plenamente seu vídeo compartilhado.",
|
"micMutedPopup": "Seu microfone está mudo assim que você<br/>pode curtir plenamente seu vídeo compartilhado.",
|
||||||
"unableToUnmutePopup": "Você não pode sair do mudo enquanto seu vídeo compartilhado está ativo.",
|
"unableToUnmutePopup": "Você não pode sair do mudo enquanto seu vídeo compartilhado está ativo.",
|
||||||
"cameraDisabled": "A câmera não está disponível",
|
"cameraDisabled": "A câmera não está disponível",
|
||||||
"micDisabled": "O microfone não está disponível"
|
"micDisabled": "O microfone não está disponível",
|
||||||
|
"filmstrip": "",
|
||||||
|
"raiseHand": "Levantar a mão para falar"
|
||||||
},
|
},
|
||||||
"bottomtoolbar": {
|
"bottomtoolbar": {
|
||||||
"chat": "Abrir / fechar bate-papo",
|
"chat": "Abrir / fechar bate-papo",
|
||||||
"filmstrip": "Mostrar / ocultar a tira de usuários",
|
"filmstrip": "Mostrar/ocultar vídeos",
|
||||||
"contactlist": "Abrir / fechar a lista de contatos"
|
"contactlist": "Abrir / fechar a lista de contatos"
|
||||||
},
|
},
|
||||||
"chat": {
|
"chat": {
|
||||||
|
@ -108,18 +108,27 @@
|
||||||
"messagebox": "Digite um texto..."
|
"messagebox": "Digite um texto..."
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"title": "CONFIGURAÇÕES",
|
"title": "Configurações",
|
||||||
"update": "Atualizar",
|
"update": "Atualizar",
|
||||||
"name": "Nome",
|
"name": "Nome",
|
||||||
"startAudioMuted": "Iniciar sem áudio",
|
"startAudioMuted": "Todos iniciam mudos",
|
||||||
"startVideoMuted": "Iniciar sem vídeo",
|
"startVideoMuted": "Todos iniciam ocultos",
|
||||||
"selectCamera": "Selecione a câmera",
|
"selectCamera": "Câmera",
|
||||||
"selectMic": "Selecionar o microfone",
|
"selectMic": "Microfone",
|
||||||
"selectAudioOutput": "Selecionar a saída de áudio",
|
"selectAudioOutput": "Saída de áudio",
|
||||||
"followMe": "Habilitar o siga-me",
|
"followMe": "Todos me seguem",
|
||||||
"noDevice": "Nenhum",
|
"noDevice": "Nenhum",
|
||||||
"noPermission": "Permissão para usar o dispositivo não concedida",
|
"noPermission": "Permissão para usar o dispositivo não concedida",
|
||||||
"avatarUrl": "URL do Avatar"
|
"cameraAndMic": "Câmera e microfone",
|
||||||
|
"moderator": "MODERADOR",
|
||||||
|
"password": "DEFINIR SENHA",
|
||||||
|
"audioVideo": "ÁUDIO E VÍDEO",
|
||||||
|
"setPasswordLabel": "Trancar sua sala com uma senha."
|
||||||
|
},
|
||||||
|
"profile": {
|
||||||
|
"title": "PERFIL",
|
||||||
|
"setDisplayNameLabel": "Definir seu nome de exibição",
|
||||||
|
"setEmailLabel": "Definir seu email de gravatar"
|
||||||
},
|
},
|
||||||
"videothumbnail": {
|
"videothumbnail": {
|
||||||
"editnickname": "Clique para editar o seu <br/>nome de exibição",
|
"editnickname": "Clique para editar o seu <br/>nome de exibição",
|
||||||
|
@ -172,6 +181,7 @@
|
||||||
"connectError": "Oops! Alguma coisa está errada e nós não pudemos conectar à conferência.",
|
"connectError": "Oops! Alguma coisa está errada e nós não pudemos conectar à conferência.",
|
||||||
"connectErrorWithMsg": "Oops! Alguma coisa está errada e não podemos conectar à conferência: __msg__",
|
"connectErrorWithMsg": "Oops! Alguma coisa está errada e não podemos conectar à conferência: __msg__",
|
||||||
"connecting": "Conectando",
|
"connecting": "Conectando",
|
||||||
|
"copy": "Copiar",
|
||||||
"error": "Erro",
|
"error": "Erro",
|
||||||
"detectext": "Erro enquanto tenta detectar a extensão de compartilhamento de tela.",
|
"detectext": "Erro enquanto tenta detectar a extensão de compartilhamento de tela.",
|
||||||
"failtoinstall": "Falhou a instalação da extensão de compartilhamento de tela",
|
"failtoinstall": "Falhou a instalação da extensão de compartilhamento de tela",
|
||||||
|
@ -183,8 +193,8 @@
|
||||||
"lockMessage": "Falha ao travar a conferência.",
|
"lockMessage": "Falha ao travar a conferência.",
|
||||||
"warning": "Atenção",
|
"warning": "Atenção",
|
||||||
"passwordNotSupported": "Senhas de salas não são suportadas atualmente.",
|
"passwordNotSupported": "Senhas de salas não são suportadas atualmente.",
|
||||||
"sorry": "Desculpe",
|
"internalErrorTitle": "Erro interno",
|
||||||
"internalError": "Erro interno de aplicação [setRemoteDescription]",
|
"internalError": "Ops! Alguma coisa está errada. Ocorreu o seguinte erro: [setRemoteDescriptio]",
|
||||||
"unableToSwitch": "Impossível trocar o fluxo de vídeo.",
|
"unableToSwitch": "Impossível trocar o fluxo de vídeo.",
|
||||||
"SLDFailure": "Oops! Alguma coisa está errada e nós falhamos em silenciar! (Falha do SLD)",
|
"SLDFailure": "Oops! Alguma coisa está errada e nós falhamos em silenciar! (Falha do SLD)",
|
||||||
"SRDFailure": "Oops! Alguma coisa está errada e nós falhamos em parar o vídeo! (Falha do SRD)",
|
"SRDFailure": "Oops! Alguma coisa está errada e nós falhamos em parar o vídeo! (Falha do SRD)",
|
||||||
|
@ -216,26 +226,28 @@
|
||||||
"sipMsg": "Digite o número SIP",
|
"sipMsg": "Digite o número SIP",
|
||||||
"passwordCheck": "Você tem certeza que deseja remover sua senha?",
|
"passwordCheck": "Você tem certeza que deseja remover sua senha?",
|
||||||
"passwordMsg": "Definir uma senha para trancar sua sala",
|
"passwordMsg": "Definir uma senha para trancar sua sala",
|
||||||
"Invite": "Convidar",
|
"shareLink": "Copiar e compartilhar este link",
|
||||||
"shareLink": "Compartilhar este link com quem você espera convidar",
|
|
||||||
"settings1": "Configure sua conferência",
|
"settings1": "Configure sua conferência",
|
||||||
"settings2": "Participantes entram mudos",
|
"settings2": "Participantes entram mudos",
|
||||||
"settings3": "Requer apelidos<br/><br/>Defina uma senha para trancar sua sala:",
|
"settings3": "Requer apelidos<br/><br/>Defina uma senha para trancar sua sala:",
|
||||||
"yourPassword": "sua Senha",
|
"yourPassword": "Digite a nova senha",
|
||||||
"Back": "Voltar",
|
"Back": "Voltar",
|
||||||
"serviceUnavailable": "Serviço indisponível",
|
"serviceUnavailable": "Serviço indisponível",
|
||||||
"gracefulShutdown": "Nosso serviço está desligado para manutenção. Por favor, tente mais tarde.",
|
"gracefulShutdown": "Nosso serviço está desligado para manutenção. Por favor, tente mais tarde.",
|
||||||
"Yes": "Sim",
|
"Yes": "Sim",
|
||||||
"reservationError": "Erro de sistema de reserva",
|
"reservationError": "Erro de sistema de reserva",
|
||||||
"reservationErrorMsg": "Código do erro: __code__, mensagem: __msg__",
|
"reservationErrorMsg": "Código do erro: __code__, mensagem: __msg__",
|
||||||
"password": "senha",
|
"password": "Insira a senha",
|
||||||
"userPassword": "senha do usuário",
|
"userPassword": "senha do usuário",
|
||||||
"token": "token",
|
"token": "token",
|
||||||
"tokenAuthFailed": "Falha em autenticar com o servidor XMPP: token inválido",
|
"tokenAuthFailedTitle": "Problema na autenticação",
|
||||||
|
"tokenAuthFailed": "Desculpe, você não está autorizado a entrar nesta chamada.",
|
||||||
"displayNameRequired": "Digite seu nome de exibição",
|
"displayNameRequired": "Digite seu nome de exibição",
|
||||||
"extensionRequired": "Extensão requerida:",
|
"extensionRequired": "Extensão requerida:",
|
||||||
"firefoxExtensionPrompt": "Você precisa instalar uma extensão do Firefox para compartilhar a tela. Tente novamente depois que você <a href='__url__'>pegá-lo aqui</a>!",
|
"firefoxExtensionPrompt": "Você precisa instalar uma extensão do Firefox para compartilhar a tela. Tente novamente depois que você <a href='__url__'>pegá-lo aqui</a>!",
|
||||||
"feedbackQuestion": "Como foi a chamada?",
|
"rateExperience": "Por favor, avalie sua experiência na reunião.",
|
||||||
|
"feedbackHelp": "Seu retorno nos ajudará a melhorar nossa experiência de vídeo.",
|
||||||
|
"feedbackQuestion": "Nos conte sobre sua chamada!",
|
||||||
"thankYou": "Obrigado por usar o __appName__!",
|
"thankYou": "Obrigado por usar o __appName__!",
|
||||||
"sorryFeedback": "Lamentamos escutar isso. Gostaria de nos contar mais?",
|
"sorryFeedback": "Lamentamos escutar isso. Gostaria de nos contar mais?",
|
||||||
"liveStreaming": "Live Streaming",
|
"liveStreaming": "Live Streaming",
|
||||||
|
@ -253,12 +265,17 @@
|
||||||
"cameraUnsupportedResolutionError": "Sua câmera não suporta a resolução de vídeo requerida.",
|
"cameraUnsupportedResolutionError": "Sua câmera não suporta a resolução de vídeo requerida.",
|
||||||
"cameraUnknownError": "Não pode usar a câmera por uma razão desconhecida.",
|
"cameraUnknownError": "Não pode usar a câmera por uma razão desconhecida.",
|
||||||
"cameraPermissionDeniedError": "Você não tem permissão para usar sua câmera. Você ainda pode entrar na conferência, mas os outros não verão você. Use o botão da câmera na barra de endereço para fixar isto.",
|
"cameraPermissionDeniedError": "Você não tem permissão para usar sua câmera. Você ainda pode entrar na conferência, mas os outros não verão você. Use o botão da câmera na barra de endereço para fixar isto.",
|
||||||
"cameraNotFoundError": "Câmera solicitada não foi encontrada.",
|
"cameraNotFoundError": "A câmera não foi encontrada.",
|
||||||
"cameraConstraintFailedError": "Sua câmera não satisfaz algumas condições requeridas.",
|
"cameraConstraintFailedError": "Sua câmera não satisfaz algumas condições requeridas.",
|
||||||
"micUnknownError": "Não pode usar o microfone por uma razão desconhecida.",
|
"micUnknownError": "Não pode usar o microfone por uma razão desconhecida.",
|
||||||
"micPermissionDeniedError": "Você não tem permissão para usar seu microfone. Você ainda pode entrar na conferência, mas os outros não ouvirão você. Use o botão da câmera na barra de endereço para fixar isto.",
|
"micPermissionDeniedError": "Você não tem permissão para usar seu microfone. Você ainda pode entrar na conferência, mas os outros não ouvirão você. Use o botão da câmera na barra de endereço para fixar isto.",
|
||||||
"micNotFoundError": "O microfone solicitado não foi encontrado.",
|
"micNotFoundError": "O microfone não foi encontrado.",
|
||||||
"micConstraintFailedError": "Seu microfone não satisfaz algumas condições requeridas."
|
"micConstraintFailedError": "Seu microfone não satisfaz algumas condições requeridas.",
|
||||||
|
"micNotSendingData": "Seu microfone está inacessível. Selecione outro dispositivo do menu de configurações ou tente reiniciar a aplicação.",
|
||||||
|
"cameraNotSendingData": "Sua câmera está inacessível. Verifique se outra aplicação está usando este dispositivo, selecione outro dispositivo do menu de configurações ou tente reiniciar a aplicação.",
|
||||||
|
"goToStore": "Vá para a loja virtual",
|
||||||
|
"externalInstallationTitle": "Extensão requerida",
|
||||||
|
"externalInstallationMsg": "Você precisa instalar nossa extensão de compartilhamento de tela."
|
||||||
},
|
},
|
||||||
"email": {
|
"email": {
|
||||||
"sharedKey": [
|
"sharedKey": [
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
{
|
{
|
||||||
"contactlist": "ON CALL (__participants__)",
|
"contactlist": "On Call",
|
||||||
"connectionsettings": "Connection Settings",
|
"connectionsettings": "Connection Settings",
|
||||||
"poweredby": "powered by",
|
"poweredby": "powered by",
|
||||||
"downloadlogs": "Download logs",
|
|
||||||
"feedback": "Give us your feedback",
|
"feedback": "Give us your feedback",
|
||||||
"roomUrlDefaultMsg": "Your conference is currently being created...",
|
"roomUrlDefaultMsg": "Your conference is currently being created...",
|
||||||
"participant": "Participant",
|
|
||||||
"me": "me",
|
"me": "me",
|
||||||
"speaker": "Speaker",
|
"speaker": "Speaker",
|
||||||
"raisedHand": "Would like to speak",
|
"raisedHand": "Would like to speak",
|
||||||
|
@ -33,6 +31,7 @@
|
||||||
"focusRemote": "Focus on one of the remote videos.",
|
"focusRemote": "Focus on one of the remote videos.",
|
||||||
"toggleChat": "Open or close the chat panel.",
|
"toggleChat": "Open or close the chat panel.",
|
||||||
"mute": "Mute or unmute the microphone.",
|
"mute": "Mute or unmute the microphone.",
|
||||||
|
"fullScreen": "Enter or exit full screen mode.",
|
||||||
"videoMute": "Stop or start the local video."
|
"videoMute": "Stop or start the local video."
|
||||||
},
|
},
|
||||||
"welcomepage":{
|
"welcomepage":{
|
||||||
|
@ -112,21 +111,21 @@
|
||||||
},
|
},
|
||||||
"settings":
|
"settings":
|
||||||
{
|
{
|
||||||
"title": "SETTINGS",
|
"title": "Settings",
|
||||||
"update": "Update",
|
"update": "Update",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"startAudioMuted": "Everyone starts muted",
|
"startAudioMuted": "Everyone starts muted",
|
||||||
"startVideoMuted": "Everyone starts hidden",
|
"startVideoMuted": "Everyone starts hidden",
|
||||||
"selectCamera": "Select camera",
|
"selectCamera": "Camera",
|
||||||
"selectMic": "Select microphone",
|
"selectMic": "Microphone",
|
||||||
"selectAudioOutput": "Select audio output",
|
"selectAudioOutput": "Audio output",
|
||||||
"followMe": "Everyone follows me",
|
"followMe": "Everyone follows me",
|
||||||
"noDevice": "None",
|
"noDevice": "None",
|
||||||
"noPermission": "Permission to use device is not granted",
|
"noPermission": "Permission to use device is not granted",
|
||||||
"cameraAndMic": "Camera and microphone",
|
"cameraAndMic": "Camera and microphone",
|
||||||
"moderator": "MODERATOR",
|
"moderator": "MODERATOR",
|
||||||
"password": "SET PASSWORD",
|
"password": "SET PASSWORD",
|
||||||
"audioVideo": "AUDIO / VIDEO",
|
"audioVideo": "AUDIO AND VIDEO",
|
||||||
"setPasswordLabel": "Lock your room with a password."
|
"setPasswordLabel": "Lock your room with a password."
|
||||||
},
|
},
|
||||||
"profile": {
|
"profile": {
|
||||||
|
@ -138,7 +137,7 @@
|
||||||
{
|
{
|
||||||
"editnickname": "Click to edit your<br/>display name",
|
"editnickname": "Click to edit your<br/>display name",
|
||||||
"moderator": "The owner of<br/>this conference",
|
"moderator": "The owner of<br/>this conference",
|
||||||
"videomute": "Participant has<br/>stopped the camera.",
|
"videomute": "Participant has<br/>stopped the camera",
|
||||||
"mute": "Participant is muted",
|
"mute": "Participant is muted",
|
||||||
"kick": "Kick out",
|
"kick": "Kick out",
|
||||||
"muted": "Muted",
|
"muted": "Muted",
|
||||||
|
@ -188,6 +187,7 @@
|
||||||
"connectError": "Oops! Something went wrong and we couldn't connect to the conference.",
|
"connectError": "Oops! Something went wrong and we couldn't connect to the conference.",
|
||||||
"connectErrorWithMsg": "Oops! Something went wrong and we couldn't connect to the conference: __msg__",
|
"connectErrorWithMsg": "Oops! Something went wrong and we couldn't connect to the conference: __msg__",
|
||||||
"connecting": "Connecting",
|
"connecting": "Connecting",
|
||||||
|
"copy": "Copy",
|
||||||
"error": "Error",
|
"error": "Error",
|
||||||
"detectext": "Error when trying to detect desktopsharing extension.",
|
"detectext": "Error when trying to detect desktopsharing extension.",
|
||||||
"failtoinstall": "Failed to install desktop sharing extension",
|
"failtoinstall": "Failed to install desktop sharing extension",
|
||||||
|
@ -199,8 +199,8 @@
|
||||||
"lockMessage": "Failed to lock the conference.",
|
"lockMessage": "Failed to lock the conference.",
|
||||||
"warning": "Warning",
|
"warning": "Warning",
|
||||||
"passwordNotSupported": "Room passwords are currently not supported.",
|
"passwordNotSupported": "Room passwords are currently not supported.",
|
||||||
"sorry": "Sorry",
|
"internalErrorTitle": "Internal error",
|
||||||
"internalError": "Internal application error [setRemoteDescription]",
|
"internalError": "Oups! Something went wrong. The following error occurred: [setRemoteDescription]",
|
||||||
"unableToSwitch": "Unable to switch video stream.",
|
"unableToSwitch": "Unable to switch video stream.",
|
||||||
"SLDFailure": "Oops! Something went wrong and we failed to mute! (SLD Failure)",
|
"SLDFailure": "Oops! Something went wrong and we failed to mute! (SLD Failure)",
|
||||||
"SRDFailure": "Oops! Something went wrong and we failed to stop video! (SRD Failure)",
|
"SRDFailure": "Oops! Something went wrong and we failed to stop video! (SRD Failure)",
|
||||||
|
@ -233,7 +233,6 @@
|
||||||
"passwordCheck": "Are you sure you would like to remove your password?",
|
"passwordCheck": "Are you sure you would like to remove your password?",
|
||||||
"Remove": "Remove",
|
"Remove": "Remove",
|
||||||
"passwordMsg": "Set a password to lock your room",
|
"passwordMsg": "Set a password to lock your room",
|
||||||
"Invite": "Invite",
|
|
||||||
"shareLink": "Copy and share this link",
|
"shareLink": "Copy and share this link",
|
||||||
"settings1": "Configure your conference",
|
"settings1": "Configure your conference",
|
||||||
"settings2": "Participants join muted",
|
"settings2": "Participants join muted",
|
||||||
|
@ -248,13 +247,14 @@
|
||||||
"password": "Enter password",
|
"password": "Enter password",
|
||||||
"userPassword": "user password",
|
"userPassword": "user password",
|
||||||
"token": "token",
|
"token": "token",
|
||||||
"tokenAuthFailed": "Failed to authenticate with XMPP server: invalid token",
|
"tokenAuthFailedTitle": "Authentication problem",
|
||||||
|
"tokenAuthFailed": "Sorry, you're not allowed to join this call.",
|
||||||
"displayNameRequired": "Please enter your display name",
|
"displayNameRequired": "Please enter your display name",
|
||||||
"extensionRequired": "Extension required:",
|
"extensionRequired": "Extension required:",
|
||||||
"firefoxExtensionPrompt": "You need to install a Firefox extension in order to use screen sharing. Please try again after you <a href='__url__'>get it from here</a>!",
|
"firefoxExtensionPrompt": "You need to install a Firefox extension in order to use screen sharing. Please try again after you <a href='__url__'>get it from here</a>!",
|
||||||
"rateExperience": "Please rate your meeting experience.",
|
"rateExperience": "Please rate your meeting experience.",
|
||||||
"feedbackHelp": "Your feedback will help us to improve our video experience.",
|
"feedbackHelp": "Your feedback will help us to improve our video experience.",
|
||||||
"feedbackQuestion": "How was your call?",
|
"feedbackQuestion": "Tell us about your call!",
|
||||||
"thankYou": "Thank you for using __appName__!",
|
"thankYou": "Thank you for using __appName__!",
|
||||||
"sorryFeedback": "We're sorry to hear that. Would you like to tell us more?",
|
"sorryFeedback": "We're sorry to hear that. Would you like to tell us more?",
|
||||||
"liveStreaming": "Live Streaming",
|
"liveStreaming": "Live Streaming",
|
||||||
|
@ -279,6 +279,7 @@
|
||||||
"micNotFoundError": "Microphone was not found.",
|
"micNotFoundError": "Microphone was not found.",
|
||||||
"micConstraintFailedError": "Yor microphone does not satisfy some of required constraints.",
|
"micConstraintFailedError": "Yor microphone does not satisfy some of required constraints.",
|
||||||
"micNotSendingData": "We are unable to access your microphone. Please select another device from the settings menu or try to restart the application.",
|
"micNotSendingData": "We are unable to access your microphone. Please select another device from the settings menu or try to restart the application.",
|
||||||
|
"cameraNotSendingData": "We are unable to access your camera. Please check if another application is using this device, select another device from the settings menu or try to restart the application.",
|
||||||
"goToStore": "Go to the webstore",
|
"goToStore": "Go to the webstore",
|
||||||
"externalInstallationTitle": "Extension required",
|
"externalInstallationTitle": "Extension required",
|
||||||
"externalInstallationMsg": "You need to install our desktop sharing extension."
|
"externalInstallationMsg": "You need to install our desktop sharing extension."
|
||||||
|
@ -325,7 +326,8 @@
|
||||||
"ATTACHED": "Attached",
|
"ATTACHED": "Attached",
|
||||||
"FETCH_SESSION_ID": "Obtaining session-id...",
|
"FETCH_SESSION_ID": "Obtaining session-id...",
|
||||||
"GOT_SESSION_ID": "Obtaining session-id... Done",
|
"GOT_SESSION_ID": "Obtaining session-id... Done",
|
||||||
"GET_SESSION_ID_ERROR": "Get session-id error: "
|
"GET_SESSION_ID_ERROR": "Get session-id error: ",
|
||||||
|
"USER_CONNECTION_INTERRUPTED": "__displayName__ is having connectivity issues..."
|
||||||
},
|
},
|
||||||
"recording":
|
"recording":
|
||||||
{
|
{
|
||||||
|
|
|
@ -97,6 +97,7 @@ class TokenData{
|
||||||
this.payload = this.decodedJWT.payload;
|
this.payload = this.decodedJWT.payload;
|
||||||
if(!this.payload.context)
|
if(!this.payload.context)
|
||||||
return;
|
return;
|
||||||
|
this.server = this.payload.context.server;
|
||||||
let callerData = this.payload.context.user;
|
let callerData = this.payload.context.user;
|
||||||
let calleeData = this.payload.context.callee;
|
let calleeData = this.payload.context.callee;
|
||||||
if(callerData)
|
if(callerData)
|
||||||
|
|
|
@ -1,321 +0,0 @@
|
||||||
/* global $, APP, config, interfaceConfig, JitsiMeetJS */
|
|
||||||
import UIEvents from "../../service/UI/UIEvents";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs the html for the overall feedback window.
|
|
||||||
*
|
|
||||||
* @returns {string} the constructed html string
|
|
||||||
*/
|
|
||||||
var constructOverallFeedbackHtml = function() {
|
|
||||||
var feedbackQuestion = (Feedback.feedbackScore < 0)
|
|
||||||
? '<br/><br/>' + APP.translation
|
|
||||||
.translateString("dialog.feedbackQuestion")
|
|
||||||
: '';
|
|
||||||
|
|
||||||
var message = '<div class="feedback"><div>' +
|
|
||||||
'<div class="feedbackTitle">' +
|
|
||||||
APP.translation.translateString("dialog.thankYou",
|
|
||||||
{appName:interfaceConfig.APP_NAME}) +
|
|
||||||
'</div>' +
|
|
||||||
feedbackQuestion +
|
|
||||||
'</div><br/><br/>' +
|
|
||||||
'<div id="stars">' +
|
|
||||||
'<a><i class="icon-star icon-star-full"></i></a>' +
|
|
||||||
'<a><i class="icon-star icon-star-full"></i></a>' +
|
|
||||||
'<a><i class="icon-star icon-star-full"></i></a>' +
|
|
||||||
'<a><i class="icon-star icon-star-full"></i></a>' +
|
|
||||||
'<a><i class="icon-star icon-star-full"></i></a>' +
|
|
||||||
'</div></div>';
|
|
||||||
|
|
||||||
return message;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs the html for the detailed feedback window.
|
|
||||||
*
|
|
||||||
* @returns {string} the contructed html string
|
|
||||||
*/
|
|
||||||
var constructDetailedFeedbackHtml = function() {
|
|
||||||
// Construct the html, which will be served as a dialog message.
|
|
||||||
var message = '<div class="feedback">' +
|
|
||||||
'<div class="feedbackTitle">' +
|
|
||||||
APP.translation.translateString("dialog.sorryFeedback") +
|
|
||||||
'</div><br/><br/>' +
|
|
||||||
'<div class="feedbackDetails">' +
|
|
||||||
'<textarea id="feedbackTextArea" rows="10" cols="50" autofocus>' +
|
|
||||||
'</textarea>' +
|
|
||||||
'</div></div>';
|
|
||||||
|
|
||||||
return message;
|
|
||||||
};
|
|
||||||
|
|
||||||
var createRateFeedbackHTML = function () {
|
|
||||||
var rate = APP.translation.translateString('dialog.rateExperience'),
|
|
||||||
help = APP.translation.translateString('dialog.feedbackHelp');
|
|
||||||
|
|
||||||
return `
|
|
||||||
<div class="feedback-rating text-center">
|
|
||||||
<h2>${ rate }</h2>
|
|
||||||
<p class="star-label"> </p>
|
|
||||||
<div id="stars" class="feedback-stars">
|
|
||||||
<a class="star-btn">
|
|
||||||
<i class="fa fa-star shake-rotate"></i>
|
|
||||||
</a>
|
|
||||||
<a class="star-btn">
|
|
||||||
<i class="fa fa-star shake-rotate"></i>
|
|
||||||
</a>
|
|
||||||
<a class="star-btn">
|
|
||||||
<i class="fa fa-star shake-rotate"></i>
|
|
||||||
</a>
|
|
||||||
<a class="star-btn">
|
|
||||||
<i class="fa fa-star shake-rotate"></i>
|
|
||||||
</a>
|
|
||||||
<a class="star-btn">
|
|
||||||
<i class="fa fa-star shake-rotate"></i>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<p> </p>
|
|
||||||
<p>${ help }</p>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The callback function corresponding to the openFeedbackWindow parameter.
|
|
||||||
*
|
|
||||||
* @type {function}
|
|
||||||
*/
|
|
||||||
var feedbackWindowCallback = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shows / hides the feedback button.
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
function _toggleFeedbackIcon() {
|
|
||||||
$('#feedbackButtonDiv').toggleClass("hidden");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shows / hides the feedback button.
|
|
||||||
* @param {show} set to {true} to show the feedback button or to {false}
|
|
||||||
* to hide it
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
function _showFeedbackButton (show) {
|
|
||||||
var feedbackButton = $("#feedbackButtonDiv");
|
|
||||||
|
|
||||||
if (show)
|
|
||||||
feedbackButton.css("display", "block");
|
|
||||||
else
|
|
||||||
feedbackButton.css("display", "none");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines all methods in connection to the Feedback window.
|
|
||||||
*
|
|
||||||
* @type {{feedbackScore: number, openFeedbackWindow: Function,
|
|
||||||
* toggleStars: Function, hoverStars: Function, unhoverStars: Function}}
|
|
||||||
*/
|
|
||||||
var Feedback = {
|
|
||||||
/**
|
|
||||||
* The feedback score. -1 indicates no score has been given for now.
|
|
||||||
*/
|
|
||||||
feedbackScore: -1,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialise the Feedback functionality.
|
|
||||||
* @param emitter the EventEmitter to associate with the Feedback.
|
|
||||||
*/
|
|
||||||
init: function (emitter) {
|
|
||||||
// CallStats is the way we send feedback, so we don't have to initialise
|
|
||||||
// if callstats isn't enabled.
|
|
||||||
if (!APP.conference.isCallstatsEnabled())
|
|
||||||
return;
|
|
||||||
|
|
||||||
// If enabled property is still undefined, i.e. it hasn't been set from
|
|
||||||
// some other module already, we set it to true by default.
|
|
||||||
if (typeof this.enabled == "undefined")
|
|
||||||
this.enabled = true;
|
|
||||||
|
|
||||||
_showFeedbackButton(this.enabled);
|
|
||||||
|
|
||||||
$("#feedbackButton").click(function (event) {
|
|
||||||
Feedback.openFeedbackWindow();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Show / hide the feedback button whenever the film strip is
|
|
||||||
// shown / hidden.
|
|
||||||
emitter.addListener(UIEvents.TOGGLE_FILM_STRIP, function () {
|
|
||||||
_toggleFeedbackIcon();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Enables/ disabled the feedback feature.
|
|
||||||
*/
|
|
||||||
enableFeedback: function (enable) {
|
|
||||||
if (this.enabled !== enable)
|
|
||||||
_showFeedbackButton(enable);
|
|
||||||
this.enabled = enable;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates if the feedback functionality is enabled.
|
|
||||||
*
|
|
||||||
* @return true if the feedback functionality is enabled, false otherwise.
|
|
||||||
*/
|
|
||||||
isEnabled: function() {
|
|
||||||
return this.enabled && APP.conference.isCallstatsEnabled();
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the feedback window is currently visible and false
|
|
||||||
* otherwise.
|
|
||||||
* @return {boolean} true if the feedback window is visible, false
|
|
||||||
* otherwise
|
|
||||||
*/
|
|
||||||
isVisible: function() {
|
|
||||||
return $(".feedback").is(":visible");
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Opens the feedback window.
|
|
||||||
*/
|
|
||||||
openFeedbackWindow: function (callback) {
|
|
||||||
feedbackWindowCallback = callback;
|
|
||||||
// Add all mouse and click listeners.
|
|
||||||
var onLoadFunction = function (event) {
|
|
||||||
$('#stars >a').each(function(index) {
|
|
||||||
// On star mouse over.
|
|
||||||
$(this).get(0).onmouseover = function(){
|
|
||||||
Feedback.hoverStars(index);
|
|
||||||
};
|
|
||||||
// On star mouse leave.
|
|
||||||
$(this).get(0).onmouseleave = function(){
|
|
||||||
Feedback.unhoverStars(index);
|
|
||||||
};
|
|
||||||
// On star click.
|
|
||||||
$(this).get(0).onclick = function(){
|
|
||||||
Feedback.toggleStars(index);
|
|
||||||
Feedback.feedbackScore = index+1;
|
|
||||||
|
|
||||||
// If the feedback is less than 3 stars we're going to
|
|
||||||
// ask the user for more information.
|
|
||||||
if (Feedback.feedbackScore > 3) {
|
|
||||||
APP.conference.sendFeedback(Feedback.feedbackScore, "");
|
|
||||||
if (feedbackWindowCallback)
|
|
||||||
feedbackWindowCallback();
|
|
||||||
else
|
|
||||||
APP.UI.messageHandler.closeDialog();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
feedbackDialog.goToState('detailed_feedback');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// Init stars to correspond to previously entered feedback.
|
|
||||||
if (Feedback.feedbackScore > 0
|
|
||||||
&& index < Feedback.feedbackScore) {
|
|
||||||
Feedback.hoverStars(index);
|
|
||||||
Feedback.toggleStars(index);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Defines the different states of the feedback window.
|
|
||||||
var states = {
|
|
||||||
overall_feedback: {
|
|
||||||
html: createRateFeedbackHTML(),
|
|
||||||
persistent: false,
|
|
||||||
buttons: {},
|
|
||||||
closeText: '',
|
|
||||||
focus: "div[id='stars']",
|
|
||||||
position: {width: 500}
|
|
||||||
},
|
|
||||||
detailed_feedback: {
|
|
||||||
html: constructDetailedFeedbackHtml(),
|
|
||||||
buttons: {"Submit": true, "Cancel": false},
|
|
||||||
closeText: '',
|
|
||||||
focus: "textarea[id='feedbackTextArea']",
|
|
||||||
position: {width: 500},
|
|
||||||
submit: function(e,v,m,f) {
|
|
||||||
e.preventDefault();
|
|
||||||
if (v) {
|
|
||||||
var feedbackDetails
|
|
||||||
= document.getElementById("feedbackTextArea").value;
|
|
||||||
|
|
||||||
if (feedbackDetails && feedbackDetails.length > 0) {
|
|
||||||
APP.conference.sendFeedback( Feedback.feedbackScore,
|
|
||||||
feedbackDetails);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (feedbackWindowCallback)
|
|
||||||
feedbackWindowCallback();
|
|
||||||
else
|
|
||||||
APP.UI.messageHandler.closeDialog();
|
|
||||||
} else {
|
|
||||||
// User cancelled
|
|
||||||
if (feedbackWindowCallback)
|
|
||||||
feedbackWindowCallback();
|
|
||||||
else
|
|
||||||
APP.UI.messageHandler.closeDialog();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create the feedback dialog.
|
|
||||||
var feedbackDialog
|
|
||||||
= APP.UI.messageHandler.openDialogWithStates(
|
|
||||||
states,
|
|
||||||
{ persistent: false,
|
|
||||||
buttons: {},
|
|
||||||
closeText: '',
|
|
||||||
loaded: onLoadFunction,
|
|
||||||
position: {width: 500}}, null);
|
|
||||||
JitsiMeetJS.analytics.sendEvent('feedback.open');
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Toggles the appropriate css class for the given number of stars, to
|
|
||||||
* indicate that those stars have been clicked/selected.
|
|
||||||
*
|
|
||||||
* @param starCount the number of stars, for which to toggle the css class
|
|
||||||
*/
|
|
||||||
toggleStars: function (starCount)
|
|
||||||
{
|
|
||||||
$('#stars >a >i').each(function(index) {
|
|
||||||
if (index <= starCount) {
|
|
||||||
$(this).removeClass("icon-star");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
$(this).addClass("icon-star");
|
|
||||||
});
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Toggles the appropriate css class for the given number of stars, to
|
|
||||||
* indicate that those stars have been hovered.
|
|
||||||
*
|
|
||||||
* @param starCount the number of stars, for which to toggle the css class
|
|
||||||
*/
|
|
||||||
hoverStars: function (starCount)
|
|
||||||
{
|
|
||||||
$('#stars >a >i').each(function(index) {
|
|
||||||
if (index <= starCount)
|
|
||||||
$(this).addClass("starHover");
|
|
||||||
});
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Toggles the appropriate css class for the given number of stars, to
|
|
||||||
* indicate that those stars have been un-hovered.
|
|
||||||
*
|
|
||||||
* @param starCount the number of stars, for which to toggle the css class
|
|
||||||
*/
|
|
||||||
unhoverStars: function (starCount)
|
|
||||||
{
|
|
||||||
$('#stars >a >i').each(function(index) {
|
|
||||||
if (index <= starCount && $(this).hasClass("icon-star"))
|
|
||||||
$(this).removeClass("starHover");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Exports the Feedback class.
|
|
||||||
module.exports = Feedback;
|
|
167
modules/UI/UI.js
167
modules/UI/UI.js
|
@ -29,7 +29,7 @@ var EventEmitter = require("events");
|
||||||
UI.messageHandler = require("./util/MessageHandler");
|
UI.messageHandler = require("./util/MessageHandler");
|
||||||
var messageHandler = UI.messageHandler;
|
var messageHandler = UI.messageHandler;
|
||||||
var JitsiPopover = require("./util/JitsiPopover");
|
var JitsiPopover = require("./util/JitsiPopover");
|
||||||
var Feedback = require("./Feedback");
|
var Feedback = require("./feedback/Feedback");
|
||||||
|
|
||||||
import FollowMe from "../FollowMe";
|
import FollowMe from "../FollowMe";
|
||||||
|
|
||||||
|
@ -60,6 +60,8 @@ JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[TrackErrors.NOT_FOUND]
|
||||||
= "dialog.cameraNotFoundError";
|
= "dialog.cameraNotFoundError";
|
||||||
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[TrackErrors.CONSTRAINT_FAILED]
|
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[TrackErrors.CONSTRAINT_FAILED]
|
||||||
= "dialog.cameraConstraintFailedError";
|
= "dialog.cameraConstraintFailedError";
|
||||||
|
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.camera[TrackErrors.NO_DATA_FROM_SOURCE]
|
||||||
|
= "dialog.cameraNotSendingData";
|
||||||
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[TrackErrors.GENERAL]
|
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[TrackErrors.GENERAL]
|
||||||
= "dialog.micUnknownError";
|
= "dialog.micUnknownError";
|
||||||
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[TrackErrors.PERMISSION_DENIED]
|
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[TrackErrors.PERMISSION_DENIED]
|
||||||
|
@ -68,6 +70,8 @@ JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[TrackErrors.NOT_FOUND]
|
||||||
= "dialog.micNotFoundError";
|
= "dialog.micNotFoundError";
|
||||||
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[TrackErrors.CONSTRAINT_FAILED]
|
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[TrackErrors.CONSTRAINT_FAILED]
|
||||||
= "dialog.micConstraintFailedError";
|
= "dialog.micConstraintFailedError";
|
||||||
|
JITSI_TRACK_ERROR_TO_MESSAGE_KEY_MAP.microphone[TrackErrors.NO_DATA_FROM_SOURCE]
|
||||||
|
= "dialog.micNotSendingData";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prompt user for nickname.
|
* Prompt user for nickname.
|
||||||
|
@ -257,6 +261,17 @@ UI.changeDisplayName = function (id, displayName) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows/hides the indication about local connection being interrupted.
|
||||||
|
*
|
||||||
|
* @param {boolean} isInterrupted <tt>true</tt> if local connection is
|
||||||
|
* currently in the interrupted state or <tt>false</tt> if the connection
|
||||||
|
* is fine.
|
||||||
|
*/
|
||||||
|
UI.showLocalConnectionInterrupted = function (isInterrupted) {
|
||||||
|
VideoLayout.showLocalConnectionInterrupted(isInterrupted);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the "raised hand" status for a participant.
|
* Sets the "raised hand" status for a participant.
|
||||||
*/
|
*/
|
||||||
|
@ -292,11 +307,11 @@ UI.initConference = function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add myself to the contact list.
|
// Add myself to the contact list.
|
||||||
ContactList.addContact(id);
|
ContactList.addContact(id, true);
|
||||||
|
|
||||||
//update default button states before showing the toolbar
|
// Update default button states before showing the toolbar
|
||||||
//if local role changes buttons state will be again updated
|
// if local role changes buttons state will be again updated.
|
||||||
UI.updateLocalRole(false);
|
UI.updateLocalRole(APP.conference.isModerator);
|
||||||
|
|
||||||
UI.showToolbar();
|
UI.showToolbar();
|
||||||
|
|
||||||
|
@ -325,6 +340,8 @@ UI.initConference = function () {
|
||||||
// to the UI (depending on the moderator role of the local participant) and
|
// to the UI (depending on the moderator role of the local participant) and
|
||||||
// (2) APP.conference as means of communication between the participants.
|
// (2) APP.conference as means of communication between the participants.
|
||||||
followMeHandler = new FollowMe(APP.conference, UI);
|
followMeHandler = new FollowMe(APP.conference, UI);
|
||||||
|
|
||||||
|
UIUtil.activateTooltips();
|
||||||
};
|
};
|
||||||
|
|
||||||
UI.mucJoined = function () {
|
UI.mucJoined = function () {
|
||||||
|
@ -339,6 +356,22 @@ UI.handleToggleFilmStrip = () => {
|
||||||
VideoLayout.resizeVideoArea(true, false);
|
VideoLayout.resizeVideoArea(true, false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets tooltip defaults.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function _setTooltipDefaults() {
|
||||||
|
$.fn.tooltip.defaults = {
|
||||||
|
opacity: 1, //defaults to 1
|
||||||
|
offset: 1,
|
||||||
|
delayIn: 0, //defaults to 500
|
||||||
|
hoverable: true,
|
||||||
|
hideOnClick: true,
|
||||||
|
aria: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setup some UI event listeners.
|
* Setup some UI event listeners.
|
||||||
*/
|
*/
|
||||||
|
@ -431,6 +464,9 @@ UI.start = function () {
|
||||||
// Set the defaults for prompt dialogs.
|
// Set the defaults for prompt dialogs.
|
||||||
$.prompt.setDefaults({persistent: false});
|
$.prompt.setDefaults({persistent: false});
|
||||||
|
|
||||||
|
// Set the defaults for tooltips.
|
||||||
|
_setTooltipDefaults();
|
||||||
|
|
||||||
registerListeners();
|
registerListeners();
|
||||||
|
|
||||||
ToolbarToggler.init();
|
ToolbarToggler.init();
|
||||||
|
@ -463,20 +499,10 @@ UI.start = function () {
|
||||||
$('#noticeText').text(config.noticeMessage);
|
$('#noticeText').text(config.noticeMessage);
|
||||||
$('#notice').css({display: 'block'});
|
$('#notice').css({display: 'block'});
|
||||||
}
|
}
|
||||||
$("#downloadlog").click(function (event) {
|
|
||||||
let logs = APP.conference.getLogs();
|
|
||||||
let data = encodeURIComponent(JSON.stringify(logs, null, ' '));
|
|
||||||
|
|
||||||
let elem = event.target.parentNode;
|
|
||||||
elem.download = 'meetlog.json';
|
|
||||||
elem.href = 'data:application/json;charset=utf-8,\n' + data;
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
$("#mainToolbarContainer").css("display", "none");
|
$("#mainToolbarContainer").css("display", "none");
|
||||||
$("#downloadlog").css("display", "none");
|
|
||||||
FilmStrip.setupFilmStripOnly();
|
FilmStrip.setupFilmStripOnly();
|
||||||
messageHandler.enableNotifications(false);
|
messageHandler.enableNotifications(false);
|
||||||
$('body').popover("disable");
|
|
||||||
JitsiPopover.enabled = false;
|
JitsiPopover.enabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -589,10 +615,11 @@ UI.getSharedDocumentManager = function () {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show user on UI.
|
* Show user on UI.
|
||||||
* @param {string} id user id
|
* @param {JitsiParticipant} user
|
||||||
* @param {string} displayName user nickname
|
|
||||||
*/
|
*/
|
||||||
UI.addUser = function (id, displayName) {
|
UI.addUser = function (user) {
|
||||||
|
var id = user.getId();
|
||||||
|
var displayName = user.getDisplayName();
|
||||||
UI.hideRingOverLay();
|
UI.hideRingOverLay();
|
||||||
ContactList.addContact(id);
|
ContactList.addContact(id);
|
||||||
|
|
||||||
|
@ -605,7 +632,7 @@ UI.addUser = function (id, displayName) {
|
||||||
UIUtil.playSoundNotification('userJoined');
|
UIUtil.playSoundNotification('userJoined');
|
||||||
|
|
||||||
// Add Peer's container
|
// Add Peer's container
|
||||||
VideoLayout.addParticipantContainer(id);
|
VideoLayout.addParticipantContainer(user);
|
||||||
|
|
||||||
// Configure avatar
|
// Configure avatar
|
||||||
UI.setUserEmail(id);
|
UI.setUserEmail(id);
|
||||||
|
@ -662,7 +689,9 @@ UI.updateLocalRole = function (isModerator) {
|
||||||
SettingsMenu.showFollowMeOptions(isModerator);
|
SettingsMenu.showFollowMeOptions(isModerator);
|
||||||
|
|
||||||
if (isModerator) {
|
if (isModerator) {
|
||||||
messageHandler.notify(null, "notify.me", 'connected', "notify.moderator");
|
if (!interfaceConfig.DISABLE_FOCUS_INDICATOR)
|
||||||
|
messageHandler
|
||||||
|
.notify(null, "notify.me", 'connected', "notify.moderator");
|
||||||
|
|
||||||
Recording.checkAutoRecord();
|
Recording.checkAutoRecord();
|
||||||
}
|
}
|
||||||
|
@ -676,7 +705,9 @@ UI.updateLocalRole = function (isModerator) {
|
||||||
UI.updateUserRole = function (user) {
|
UI.updateUserRole = function (user) {
|
||||||
VideoLayout.showModeratorIndicator();
|
VideoLayout.showModeratorIndicator();
|
||||||
|
|
||||||
if (!user.isModerator()) {
|
// We don't need to show moderator notifications when the focus (moderator)
|
||||||
|
// indicator is disabled.
|
||||||
|
if (!user.isModerator() || interfaceConfig.DISABLE_FOCUS_INDICATOR) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -970,6 +1001,17 @@ UI.handleLastNEndpoints = function (ids, enteringIds) {
|
||||||
VideoLayout.onLastNEndpointsChanged(ids, enteringIds);
|
VideoLayout.onLastNEndpointsChanged(ids, enteringIds);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will handle notification about participant's connectivity status change.
|
||||||
|
*
|
||||||
|
* @param {string} id the id of remote participant(MUC jid)
|
||||||
|
* @param {boolean} isActive true if the connection is ok or false if the user
|
||||||
|
* is having connectivity issues.
|
||||||
|
*/
|
||||||
|
UI.participantConnectionStatusChanged = function (id, isActive) {
|
||||||
|
VideoLayout.onParticipantConnectionStatusChanged(id, isActive);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update audio level visualization for specified user.
|
* Update audio level visualization for specified user.
|
||||||
* @param {string} id user id
|
* @param {string} id user id
|
||||||
|
@ -1052,50 +1094,6 @@ UI.updateDTMFSupport = function (isDTMFSupported) {
|
||||||
//Toolbar.showDialPadButton(dtmfSupport);
|
//Toolbar.showDialPadButton(dtmfSupport);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Invite participants to conference.
|
|
||||||
* @param {string} roomUrl
|
|
||||||
* @param {string} conferenceName
|
|
||||||
* @param {string} key
|
|
||||||
* @param {string} nick
|
|
||||||
*/
|
|
||||||
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, Firefox ${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" + UIUtil.escapeHtml(nick);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (interfaceConfig.INVITATION_POWERED_BY) {
|
|
||||||
body += "%0D%0A%0D%0A--%0D%0Apowered by jitsi.org";
|
|
||||||
}
|
|
||||||
|
|
||||||
window.open(`mailto:?subject=${subject}&body=${body}`, '_blank');
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show user feedback dialog if its required or just show "thank you" dialog.
|
* Show user feedback dialog if its required or just show "thank you" dialog.
|
||||||
* @returns {Promise} when dialog is closed.
|
* @returns {Promise} when dialog is closed.
|
||||||
|
@ -1103,12 +1101,15 @@ UI.inviteParticipants = function (roomUrl, conferenceName, key, nick) {
|
||||||
UI.requestFeedback = function () {
|
UI.requestFeedback = function () {
|
||||||
if (Feedback.isVisible())
|
if (Feedback.isVisible())
|
||||||
return Promise.reject(UIErrors.FEEDBACK_REQUEST_IN_PROGRESS);
|
return Promise.reject(UIErrors.FEEDBACK_REQUEST_IN_PROGRESS);
|
||||||
|
// Feedback has been submitted already.
|
||||||
|
else if (Feedback.isEnabled() && Feedback.isSubmitted())
|
||||||
|
return Promise.resolve();
|
||||||
else
|
else
|
||||||
return new Promise(function (resolve, reject) {
|
return new Promise(function (resolve, reject) {
|
||||||
if (Feedback.isEnabled()) {
|
if (Feedback.isEnabled()) {
|
||||||
// If the user has already entered feedback, we'll show the
|
// If the user has already entered feedback, we'll show the
|
||||||
// window and immidiately start the conference dispose timeout.
|
// window and immidiately start the conference dispose timeout.
|
||||||
if (Feedback.feedbackScore > 0) {
|
if (Feedback.getFeedbackScore() > 0) {
|
||||||
Feedback.openFeedbackWindow();
|
Feedback.openFeedbackWindow();
|
||||||
resolve();
|
resolve();
|
||||||
|
|
||||||
|
@ -1117,14 +1118,9 @@ UI.requestFeedback = function () {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// If the feedback functionality isn't enabled we show a thank
|
// If the feedback functionality isn't enabled we show a thank
|
||||||
// you dialog.
|
// you dialog. Signaling it (true), so the caller
|
||||||
messageHandler.openMessageDialog(
|
// of requestFeedback can act on it
|
||||||
null, null, null,
|
resolve(true);
|
||||||
APP.translation.translateString(
|
|
||||||
"dialog.thankYou", {appName:interfaceConfig.APP_NAME}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
resolve();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -1134,11 +1130,13 @@ UI.updateRecordingState = function (state) {
|
||||||
};
|
};
|
||||||
|
|
||||||
UI.notifyTokenAuthFailed = function () {
|
UI.notifyTokenAuthFailed = function () {
|
||||||
messageHandler.showError("dialog.error", "dialog.tokenAuthFailed");
|
messageHandler.showError( "dialog.tokenAuthFailedTitle",
|
||||||
|
"dialog.tokenAuthFailed");
|
||||||
};
|
};
|
||||||
|
|
||||||
UI.notifyInternalError = function () {
|
UI.notifyInternalError = function () {
|
||||||
messageHandler.showError("dialog.sorry", "dialog.internalError");
|
messageHandler.showError( "dialog.internalErrorTitle",
|
||||||
|
"dialog.internalError");
|
||||||
};
|
};
|
||||||
|
|
||||||
UI.notifyFocusDisconnected = function (focus, retrySec) {
|
UI.notifyFocusDisconnected = function (focus, retrySec) {
|
||||||
|
@ -1193,6 +1191,16 @@ UI.onStartMutedChanged = function (startAudioMuted, startVideoMuted) {
|
||||||
SettingsMenu.updateStartMutedBox(startAudioMuted, startVideoMuted);
|
SettingsMenu.updateStartMutedBox(startAudioMuted, startVideoMuted);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies interested listeners that the raise hand property has changed.
|
||||||
|
*
|
||||||
|
* @param {boolean} isRaisedHand indicates the current state of the
|
||||||
|
* "raised hand"
|
||||||
|
*/
|
||||||
|
UI.onLocalRaiseHandChanged = function (isRaisedHand) {
|
||||||
|
eventEmitter.emit(UIEvents.LOCAL_RAISE_HAND_CHANGED, isRaisedHand);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update list of available physical devices.
|
* Update list of available physical devices.
|
||||||
* @param {object[]} devices new list of available devices
|
* @param {object[]} devices new list of available devices
|
||||||
|
@ -1415,12 +1423,13 @@ UI.showDeviceErrorDialog = function (micError, cameraError) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows error dialog that informs the user that no data is received from the
|
* Shows error dialog that informs the user that no data is received from the
|
||||||
* microphone.
|
* device.
|
||||||
*/
|
*/
|
||||||
UI.showAudioNotWorkingDialog = function () {
|
UI.showTrackNotWorkingDialog = function (stream) {
|
||||||
messageHandler.openMessageDialog(
|
messageHandler.openMessageDialog(
|
||||||
"dialog.error",
|
"dialog.error",
|
||||||
"dialog.micNotSendingData",
|
stream.isAudioTrack()? "dialog.micNotSendingData" :
|
||||||
|
"dialog.cameraNotSendingData",
|
||||||
null,
|
null,
|
||||||
null);
|
null);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,260 +1,165 @@
|
||||||
/* global APP, interfaceConfig, $ */
|
/* global interfaceConfig */
|
||||||
/* jshint -W101 */
|
|
||||||
|
|
||||||
import CanvasUtil from './CanvasUtils';
|
import UIUtil from "../util/UIUtil";
|
||||||
import FilmStrip from '../videolayout/FilmStrip';
|
|
||||||
|
|
||||||
const LOCAL_LEVEL = 'local';
|
|
||||||
|
|
||||||
let ASDrawContext = null;
|
|
||||||
let audioLevelCanvasCache = {};
|
|
||||||
let dominantSpeakerAudioElement = null;
|
|
||||||
|
|
||||||
function initDominantSpeakerAudioLevels(dominantSpeakerAvatarSize) {
|
|
||||||
let ASRadius = dominantSpeakerAvatarSize / 2;
|
|
||||||
let ASCenter = (dominantSpeakerAvatarSize + ASRadius) / 2;
|
|
||||||
|
|
||||||
// Draw a circle.
|
|
||||||
ASDrawContext.beginPath();
|
|
||||||
ASDrawContext.arc(ASCenter, ASCenter, ASRadius, 0, 2 * Math.PI);
|
|
||||||
ASDrawContext.closePath();
|
|
||||||
|
|
||||||
// Add a shadow around the circle
|
|
||||||
ASDrawContext.shadowColor = interfaceConfig.SHADOW_COLOR;
|
|
||||||
ASDrawContext.shadowOffsetX = 0;
|
|
||||||
ASDrawContext.shadowOffsetY = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resizes the given audio level canvas to match the given thumbnail size.
|
* Responsible for drawing audio levels.
|
||||||
*/
|
|
||||||
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 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]) {
|
|
||||||
|
|
||||||
let videoSpanId = getVideoSpanId(id);
|
|
||||||
|
|
||||||
let 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[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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The audio Levels plugin.
|
|
||||||
*/
|
*/
|
||||||
const AudioLevels = {
|
const AudioLevels = {
|
||||||
|
|
||||||
init () {
|
/**
|
||||||
dominantSpeakerAudioElement = $('#dominantSpeakerAudioLevel')[0];
|
* The number of dots.
|
||||||
ASDrawContext = dominantSpeakerAudioElement.getContext('2d');
|
*
|
||||||
|
* IMPORTANT: functions below assume that this is an odd number.
|
||||||
let parentContainer = $("#dominantSpeaker");
|
*/
|
||||||
let dominantSpeakerWidth = parentContainer.width();
|
_AUDIO_LEVEL_DOTS: 5,
|
||||||
let dominantSpeakerHeight = parentContainer.height();
|
|
||||||
|
|
||||||
dominantSpeakerAudioElement.width = dominantSpeakerWidth;
|
|
||||||
dominantSpeakerAudioElement.height = dominantSpeakerHeight;
|
|
||||||
|
|
||||||
let dominantSpeakerAvatar = $("#dominantSpeakerAvatar");
|
|
||||||
initDominantSpeakerAudioLevels(dominantSpeakerAvatar.width());
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the audio level canvas for the given id. If the canvas
|
* Creates the audio level indicator span element.
|
||||||
* didn't exist we create it.
|
*
|
||||||
|
* IMPORTANT: This function assumes that the number of dots is an
|
||||||
|
* odd number.
|
||||||
|
*
|
||||||
|
* @return {Element} the document element representing audio levels
|
||||||
*/
|
*/
|
||||||
updateAudioLevelCanvas (id, thumbWidth, thumbHeight) {
|
createThumbnailAudioLevelIndicator() {
|
||||||
let videoSpanId = 'localVideoContainer';
|
|
||||||
if (id) {
|
let audioSpan = document.createElement('span');
|
||||||
videoSpanId = `participant_${id}`;
|
audioSpan.className = 'audioindicator';
|
||||||
}
|
|
||||||
|
this.sideDotsCount = Math.floor(this._AUDIO_LEVEL_DOTS/2);
|
||||||
let videoSpan = document.getElementById(videoSpanId);
|
|
||||||
|
for (let i = 0; i < this._AUDIO_LEVEL_DOTS; i++) {
|
||||||
if (!videoSpan) {
|
let audioDot = document.createElement('span');
|
||||||
if (id) {
|
|
||||||
console.error("No video element for id", id);
|
// The median index will be equal to the number of dots on each
|
||||||
} else {
|
// side.
|
||||||
console.error("No video element for local video.");
|
if (i === this.sideDotsCount)
|
||||||
}
|
audioDot.className = "audiodot-middle";
|
||||||
return;
|
else
|
||||||
}
|
audioDot.className = (i < this.sideDotsCount)
|
||||||
|
? "audiodot-top"
|
||||||
let audioLevelCanvas = $(`#${videoSpanId}>canvas`);
|
: "audiodot-bottom";
|
||||||
|
|
||||||
if (!audioLevelCanvas || audioLevelCanvas.length === 0) {
|
audioSpan.appendChild(audioDot);
|
||||||
|
|
||||||
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, thumbWidth, thumbHeight);
|
|
||||||
|
|
||||||
videoSpan.appendChild(audioLevelCanvas);
|
|
||||||
} else {
|
|
||||||
audioLevelCanvas = audioLevelCanvas.get(0);
|
|
||||||
|
|
||||||
resizeAudioLevelCanvas(audioLevelCanvas, thumbWidth, thumbHeight);
|
|
||||||
}
|
}
|
||||||
|
return audioSpan;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the audio level UI for the given id.
|
* Updates the audio level UI for the given id.
|
||||||
*
|
*
|
||||||
* @param id id of the user for whom we draw the audio level
|
* @param {string} id id of the user for whom we draw the audio level
|
||||||
* @param audioLevel the newAudio level to render
|
* @param {number} audioLevel the newAudio level to render
|
||||||
*/
|
*/
|
||||||
updateAudioLevel (id, audioLevel, largeVideoId) {
|
updateThumbnailAudioLevel (id, audioLevel) {
|
||||||
drawAudioLevelCanvas(id, audioLevel);
|
|
||||||
|
|
||||||
let videoSpanId = getVideoSpanId(id);
|
// First make sure we are sensitive enough.
|
||||||
|
audioLevel *= 1.2;
|
||||||
|
audioLevel = Math.min(audioLevel, 1);
|
||||||
|
|
||||||
let audioLevelCanvas = $(`#${videoSpanId}>canvas`).get(0);
|
// Let's now stretch the audio level over the number of dots we have.
|
||||||
|
let stretchedAudioLevel = (this.sideDotsCount + 1) * audioLevel;
|
||||||
|
let dotLevel = 0.0;
|
||||||
|
|
||||||
if (!audioLevelCanvas) {
|
for (let i = 0; i < (this.sideDotsCount + 1); i++) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let drawContext = audioLevelCanvas.getContext('2d');
|
dotLevel = Math.min(1, Math.max(0, (stretchedAudioLevel - i)));
|
||||||
|
this._setDotLevel(id, i, dotLevel);
|
||||||
let canvasCache = audioLevelCanvasCache[id];
|
|
||||||
|
|
||||||
drawContext.clearRect(
|
|
||||||
0, 0, audioLevelCanvas.width, audioLevelCanvas.height
|
|
||||||
);
|
|
||||||
drawContext.drawImage(canvasCache, 0, 0);
|
|
||||||
|
|
||||||
if (id === LOCAL_LEVEL) {
|
|
||||||
id = APP.conference.getMyUserId();
|
|
||||||
if (!id) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(id === largeVideoId) {
|
|
||||||
window.requestAnimationFrame(function () {
|
|
||||||
AudioLevels.updateDominantSpeakerAudioLevel(audioLevel);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
updateDominantSpeakerAudioLevel (audioLevel) {
|
/**
|
||||||
if($("#dominantSpeaker").css("visibility") == "hidden"
|
* Fills the dot(s) with the specified "index", with as much opacity as
|
||||||
|| ASDrawContext === null) {
|
* indicated by "opacity".
|
||||||
|
*
|
||||||
|
* @param {string} elementID the parent audio indicator span element
|
||||||
|
* @param {number} index the index of the dots to fill, where 0 indicates
|
||||||
|
* the middle dot and the following increments point toward the
|
||||||
|
* corresponding pair of dots.
|
||||||
|
* @param {number} opacity the opacity to set for the specified dot.
|
||||||
|
*/
|
||||||
|
_setDotLevel(elementID, index, opacity) {
|
||||||
|
|
||||||
|
let audioSpan = document.getElementById(elementID)
|
||||||
|
.getElementsByClassName("audioindicator");
|
||||||
|
|
||||||
|
// Make sure the audio span is still around.
|
||||||
|
if (audioSpan && audioSpan.length > 0)
|
||||||
|
audioSpan = audioSpan[0];
|
||||||
|
else
|
||||||
|
return;
|
||||||
|
|
||||||
|
let audioTopDots
|
||||||
|
= audioSpan.getElementsByClassName("audiodot-top");
|
||||||
|
let audioDotMiddle
|
||||||
|
= audioSpan.getElementsByClassName("audiodot-middle");
|
||||||
|
let audioBottomDots
|
||||||
|
= audioSpan.getElementsByClassName("audiodot-bottom");
|
||||||
|
|
||||||
|
// First take care of the middle dot case.
|
||||||
|
if (index === 0){
|
||||||
|
audioDotMiddle[0].style.opacity = opacity;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ASDrawContext.clearRect(0, 0,
|
// Index > 0 : we are setting non-middle dots.
|
||||||
dominantSpeakerAudioElement.width,
|
index--;
|
||||||
dominantSpeakerAudioElement.height);
|
audioBottomDots[index].style.opacity = opacity;
|
||||||
|
audioTopDots[this.sideDotsCount - index - 1].style.opacity = opacity;
|
||||||
if (!audioLevel) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ASDrawContext.shadowBlur = getShadowLevel(audioLevel);
|
|
||||||
|
|
||||||
// Fill the shape.
|
|
||||||
ASDrawContext.fill();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
updateCanvasSize (thumbWidth, thumbHeight) {
|
/**
|
||||||
let canvasWidth = thumbWidth + interfaceConfig.CANVAS_EXTRA;
|
* Updates the audio level of the large video.
|
||||||
let canvasHeight = thumbHeight + interfaceConfig.CANVAS_EXTRA;
|
*
|
||||||
|
* @param audioLevel the new audio level to set.
|
||||||
|
*/
|
||||||
|
updateLargeVideoAudioLevel(elementId, audioLevel) {
|
||||||
|
let element = document.getElementById(elementId);
|
||||||
|
|
||||||
FilmStrip.getThumbs().children('canvas').each(function () {
|
if(!UIUtil.isVisible(element))
|
||||||
$(this).attr('width', canvasWidth);
|
return;
|
||||||
$(this).attr('height', canvasHeight);
|
|
||||||
});
|
|
||||||
|
|
||||||
Object.keys(audioLevelCanvasCache).forEach(function (id) {
|
let level = parseFloat(audioLevel);
|
||||||
audioLevelCanvasCache[id].width = canvasWidth;
|
|
||||||
audioLevelCanvasCache[id].height = canvasHeight;
|
level = isNaN(level) ? 0 : level;
|
||||||
});
|
|
||||||
|
let shadowElement = element.getElementsByClassName("dynamic-shadow");
|
||||||
|
|
||||||
|
if (shadowElement && shadowElement.length > 0)
|
||||||
|
shadowElement = shadowElement[0];
|
||||||
|
|
||||||
|
shadowElement.style.boxShadow = this._updateLargeVideoShadow(level);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the large video shadow effect.
|
||||||
|
*/
|
||||||
|
_updateLargeVideoShadow (level) {
|
||||||
|
var scale = 2,
|
||||||
|
|
||||||
|
// Internal circle audio level.
|
||||||
|
int = {
|
||||||
|
level: level > 0.15 ? 20 : 0,
|
||||||
|
color: interfaceConfig.AUDIO_LEVEL_PRIMARY_COLOR
|
||||||
|
},
|
||||||
|
|
||||||
|
// External circle audio level.
|
||||||
|
ext = {
|
||||||
|
level: (int.level * scale * level + int.level).toFixed(0),
|
||||||
|
color: interfaceConfig.AUDIO_LEVEL_SECONDARY_COLOR
|
||||||
|
};
|
||||||
|
|
||||||
|
// Internal blur.
|
||||||
|
int.blur = int.level ? 2 : 0;
|
||||||
|
|
||||||
|
// External blur.
|
||||||
|
ext.blur = ext.level ? 6 : 0;
|
||||||
|
|
||||||
|
return [
|
||||||
|
`0 0 ${ int.blur }px ${ int.level }px ${ int.color }`,
|
||||||
|
`0 0 ${ ext.blur }px ${ ext.level }px ${ ext.color }`
|
||||||
|
].join(', ');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,108 +0,0 @@
|
||||||
/**
|
|
||||||
* Utility class for drawing canvas shapes.
|
|
||||||
*/
|
|
||||||
const CanvasUtil = {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Draws a round rectangle with a glow. The glowWidth indicates the depth
|
|
||||||
* of the glow.
|
|
||||||
*
|
|
||||||
* @param drawContext the context of the canvas to draw to
|
|
||||||
* @param x the x coordinate of the round rectangle
|
|
||||||
* @param y the y coordinate of the round rectangle
|
|
||||||
* @param w the width of the round rectangle
|
|
||||||
* @param h the height of the round rectangle
|
|
||||||
* @param glowColor the color of the glow
|
|
||||||
* @param glowWidth the width of the glow
|
|
||||||
*/
|
|
||||||
drawRoundRectGlow (drawContext, x, y, w, h, r, glowColor, glowWidth) {
|
|
||||||
|
|
||||||
// Save the previous state of the context.
|
|
||||||
drawContext.save();
|
|
||||||
|
|
||||||
if (w < 2 * r) r = w / 2;
|
|
||||||
if (h < 2 * r) r = h / 2;
|
|
||||||
|
|
||||||
// Draw a round rectangle.
|
|
||||||
drawContext.beginPath();
|
|
||||||
drawContext.moveTo(x+r, y);
|
|
||||||
drawContext.arcTo(x+w, y, x+w, y+h, r);
|
|
||||||
drawContext.arcTo(x+w, y+h, x, y+h, r);
|
|
||||||
drawContext.arcTo(x, y+h, x, y, r);
|
|
||||||
drawContext.arcTo(x, y, x+w, y, r);
|
|
||||||
drawContext.closePath();
|
|
||||||
|
|
||||||
// Add a shadow around the rectangle
|
|
||||||
drawContext.shadowColor = glowColor;
|
|
||||||
drawContext.shadowBlur = glowWidth;
|
|
||||||
drawContext.shadowOffsetX = 0;
|
|
||||||
drawContext.shadowOffsetY = 0;
|
|
||||||
|
|
||||||
// Fill the shape.
|
|
||||||
drawContext.fill();
|
|
||||||
|
|
||||||
drawContext.save();
|
|
||||||
|
|
||||||
drawContext.restore();
|
|
||||||
|
|
||||||
// 1) Uncomment this line to use Composite Operation, which is doing the
|
|
||||||
// same as the clip function below and is also antialiasing the round
|
|
||||||
// border, but is said to be less fast performance wise.
|
|
||||||
|
|
||||||
// drawContext.globalCompositeOperation='destination-out';
|
|
||||||
|
|
||||||
drawContext.beginPath();
|
|
||||||
drawContext.moveTo(x+r, y);
|
|
||||||
drawContext.arcTo(x+w, y, x+w, y+h, r);
|
|
||||||
drawContext.arcTo(x+w, y+h, x, y+h, r);
|
|
||||||
drawContext.arcTo(x, y+h, x, y, r);
|
|
||||||
drawContext.arcTo(x, y, x+w, y, r);
|
|
||||||
drawContext.closePath();
|
|
||||||
|
|
||||||
// 2) Uncomment this line to use Composite Operation, which is doing the
|
|
||||||
// same as the clip function below and is also antialiasing the round
|
|
||||||
// border, but is said to be less fast performance wise.
|
|
||||||
|
|
||||||
// drawContext.fill();
|
|
||||||
|
|
||||||
// Comment these two lines if choosing to do the same with composite
|
|
||||||
// operation above 1 and 2.
|
|
||||||
drawContext.clip();
|
|
||||||
drawContext.clearRect(0, 0, 277, 200);
|
|
||||||
|
|
||||||
// Restore the previous context state.
|
|
||||||
drawContext.restore();
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clones the given canvas.
|
|
||||||
*
|
|
||||||
* @return the new cloned canvas.
|
|
||||||
*/
|
|
||||||
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
|
|
||||||
* levels are frequently updated, the errors have been observed to pile
|
|
||||||
* into the console, strain the CPU.
|
|
||||||
*/
|
|
||||||
if (!oldCanvas)
|
|
||||||
return oldCanvas;
|
|
||||||
|
|
||||||
//create a new canvas
|
|
||||||
var newCanvas = document.createElement('canvas');
|
|
||||||
var context = newCanvas.getContext('2d');
|
|
||||||
|
|
||||||
//set dimensions
|
|
||||||
newCanvas.width = oldCanvas.width;
|
|
||||||
newCanvas.height = oldCanvas.height;
|
|
||||||
|
|
||||||
//apply the old canvas to the new one
|
|
||||||
context.drawImage(oldCanvas, 0, 0);
|
|
||||||
|
|
||||||
//return the new canvas
|
|
||||||
return newCanvas;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default CanvasUtil;
|
|
|
@ -116,6 +116,13 @@ export default function createRoomLocker (room) {
|
||||||
let password;
|
let password;
|
||||||
let dialog = null;
|
let dialog = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the room was locked from someone other than us, we indicate it with
|
||||||
|
* this property in order to have correct roomLocker state of isLocked.
|
||||||
|
* @type {boolean} whether room is locked, but not from us.
|
||||||
|
*/
|
||||||
|
let lockedElsewhere = false;
|
||||||
|
|
||||||
function lock (newPass) {
|
function lock (newPass) {
|
||||||
return room.lock(newPass).then(function () {
|
return room.lock(newPass).then(function () {
|
||||||
password = newPass;
|
password = newPass;
|
||||||
|
@ -135,13 +142,30 @@ export default function createRoomLocker (room) {
|
||||||
*/
|
*/
|
||||||
return {
|
return {
|
||||||
get isLocked () {
|
get isLocked () {
|
||||||
return !!password;
|
return !!password || lockedElsewhere;
|
||||||
},
|
},
|
||||||
|
|
||||||
get password () {
|
get password () {
|
||||||
return password;
|
return password;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets that the room is locked from another user, not us.
|
||||||
|
* @param {boolean} value locked/unlocked state
|
||||||
|
*/
|
||||||
|
set lockedElsewhere (value) {
|
||||||
|
lockedElsewhere = value;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether room is locked from someone else.
|
||||||
|
* @returns {boolean} whether room is not locked locally,
|
||||||
|
* but it is still locked.
|
||||||
|
*/
|
||||||
|
get lockedElsewhere () {
|
||||||
|
return lockedElsewhere;
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows to remove password from the conference (asks user first).
|
* Allows to remove password from the conference (asks user first).
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
|
@ -185,6 +209,10 @@ export default function createRoomLocker (room) {
|
||||||
newPass => { password = newPass; }
|
newPass => { password = newPass; }
|
||||||
).catch(
|
).catch(
|
||||||
reason => {
|
reason => {
|
||||||
|
// user canceled, no pass was entered.
|
||||||
|
// clear, as if we use the same instance several times
|
||||||
|
// pass stays between attempts
|
||||||
|
password = null;
|
||||||
if (reason !== APP.UI.messageHandler.CANCEL)
|
if (reason !== APP.UI.messageHandler.CANCEL)
|
||||||
console.error(reason);
|
console.error(reason);
|
||||||
}
|
}
|
||||||
|
@ -202,7 +230,7 @@ export default function createRoomLocker (room) {
|
||||||
dialog = null;
|
dialog = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (password) {
|
if (this.isLocked) {
|
||||||
dialog = APP.UI.messageHandler
|
dialog = APP.UI.messageHandler
|
||||||
.openMessageDialog(null, "dialog.passwordError",
|
.openMessageDialog(null, "dialog.passwordError",
|
||||||
null, null, closeCallback);
|
null, null, closeCallback);
|
||||||
|
|
|
@ -0,0 +1,128 @@
|
||||||
|
/* global $, APP, config, interfaceConfig, JitsiMeetJS */
|
||||||
|
import UIEvents from "../../../service/UI/UIEvents";
|
||||||
|
import FeedabckWindow from "./FeedbackWindow";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows / hides the feedback button.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function _toggleFeedbackIcon() {
|
||||||
|
$('#feedbackButtonDiv').toggleClass("hidden");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows / hides the feedback button.
|
||||||
|
* @param {show} set to {true} to show the feedback button or to {false}
|
||||||
|
* to hide it
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
function _showFeedbackButton (show) {
|
||||||
|
var feedbackButton = $("#feedbackButtonDiv");
|
||||||
|
|
||||||
|
if (show)
|
||||||
|
feedbackButton.css("display", "block");
|
||||||
|
else
|
||||||
|
feedbackButton.css("display", "none");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines all methods in connection to the Feedback window.
|
||||||
|
*
|
||||||
|
* @type {{openFeedbackWindow: Function}}
|
||||||
|
*/
|
||||||
|
var Feedback = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialise the Feedback functionality.
|
||||||
|
* @param emitter the EventEmitter to associate with the Feedback.
|
||||||
|
*/
|
||||||
|
init: function (emitter) {
|
||||||
|
// CallStats is the way we send feedback, so we don't have to initialise
|
||||||
|
// if callstats isn't enabled.
|
||||||
|
if (!APP.conference.isCallstatsEnabled())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// If enabled property is still undefined, i.e. it hasn't been set from
|
||||||
|
// some other module already, we set it to true by default.
|
||||||
|
if (typeof this.enabled == "undefined")
|
||||||
|
this.enabled = true;
|
||||||
|
|
||||||
|
_showFeedbackButton(this.enabled);
|
||||||
|
|
||||||
|
this.window = new FeedabckWindow({});
|
||||||
|
|
||||||
|
$("#feedbackButton").click(Feedback.openFeedbackWindow);
|
||||||
|
|
||||||
|
// Show / hide the feedback button whenever the film strip is
|
||||||
|
// shown / hidden.
|
||||||
|
emitter.addListener(UIEvents.TOGGLE_FILM_STRIP, function () {
|
||||||
|
_toggleFeedbackIcon();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Enables/ disabled the feedback feature.
|
||||||
|
*/
|
||||||
|
enableFeedback: function (enable) {
|
||||||
|
if (this.enabled !== enable)
|
||||||
|
_showFeedbackButton(enable);
|
||||||
|
this.enabled = enable;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the feedback functionality is enabled.
|
||||||
|
*
|
||||||
|
* @return true if the feedback functionality is enabled, false otherwise.
|
||||||
|
*/
|
||||||
|
isEnabled: function() {
|
||||||
|
return this.enabled && APP.conference.isCallstatsEnabled();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the feedback window is currently visible and false
|
||||||
|
* otherwise.
|
||||||
|
* @return {boolean} true if the feedback window is visible, false
|
||||||
|
* otherwise
|
||||||
|
*/
|
||||||
|
isVisible: function() {
|
||||||
|
return $(".feedback").is(":visible");
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the feedback is submitted.
|
||||||
|
*
|
||||||
|
* @return {boolean} {true} to indicate if the feedback is submitted,
|
||||||
|
* {false} - otherwise
|
||||||
|
*/
|
||||||
|
isSubmitted: function() {
|
||||||
|
return Feedback.window.submitted;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens the feedback window.
|
||||||
|
*/
|
||||||
|
openFeedbackWindow: function (callback) {
|
||||||
|
Feedback.window.show(callback);
|
||||||
|
|
||||||
|
JitsiMeetJS.analytics.sendEvent('feedback.open');
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the feedback score.
|
||||||
|
*
|
||||||
|
* @returns {*}
|
||||||
|
*/
|
||||||
|
getFeedbackScore: function() {
|
||||||
|
return Feedback.window.feedbackScore;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the feedback free text.
|
||||||
|
*
|
||||||
|
* @returns {null|*|message}
|
||||||
|
*/
|
||||||
|
getFeedbackText: function() {
|
||||||
|
return Feedback.window.feedbackText;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = Feedback;
|
|
@ -0,0 +1,193 @@
|
||||||
|
/* global $, APP, interfaceConfig, AJS */
|
||||||
|
/* jshint -W101 */
|
||||||
|
|
||||||
|
const selector = '#aui-feedback-dialog';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles the appropriate css class for the given number of stars, to
|
||||||
|
* indicate that those stars have been clicked/selected.
|
||||||
|
*
|
||||||
|
* @param starCount the number of stars, for which to toggle the css class
|
||||||
|
*/
|
||||||
|
let toggleStars = function(starCount) {
|
||||||
|
$('#stars > a').each(function(index, el) {
|
||||||
|
if (index <= starCount) {
|
||||||
|
el.classList.add("starHover");
|
||||||
|
} else
|
||||||
|
el.classList.remove("starHover");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs the html for the rated feedback window.
|
||||||
|
*
|
||||||
|
* @returns {string} the contructed html string
|
||||||
|
*/
|
||||||
|
let createRateFeedbackHTML = function (Feedback) {
|
||||||
|
let rateExperience
|
||||||
|
= APP.translation.translateString('dialog.rateExperience'),
|
||||||
|
feedbackHelp = APP.translation.translateString('dialog.feedbackHelp');
|
||||||
|
|
||||||
|
let starClassName = (interfaceConfig.ENABLE_FEEDBACK_ANIMATION)
|
||||||
|
? "icon-star shake-rotate"
|
||||||
|
: "icon-star";
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div class="aui-dialog2-content feedback__content">
|
||||||
|
<form action="javascript:false;" onsubmit="return false;">
|
||||||
|
<div class="feedback__rating">
|
||||||
|
<h2>${ rateExperience }</h2>
|
||||||
|
<p class="star-label"> </p>
|
||||||
|
<div id="stars" class="feedback-stars">
|
||||||
|
<a class="star-btn">
|
||||||
|
<i class=${ starClassName }></i>
|
||||||
|
</a>
|
||||||
|
<a class="star-btn">
|
||||||
|
<i class=${ starClassName }></i>
|
||||||
|
</a>
|
||||||
|
<a class="star-btn">
|
||||||
|
<i class=${ starClassName }></i>
|
||||||
|
</a>
|
||||||
|
<a class="star-btn">
|
||||||
|
<i class=${ starClassName }></i>
|
||||||
|
</a>
|
||||||
|
<a class="star-btn">
|
||||||
|
<i class=${ starClassName }></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<p> </p>
|
||||||
|
<p>${ feedbackHelp }</p>
|
||||||
|
</div>
|
||||||
|
<textarea id="feedbackTextArea" rows="10" cols="40" autofocus></textarea>
|
||||||
|
</form>
|
||||||
|
<footer class="aui-dialog2-footer feedback__footer">
|
||||||
|
<div class="aui-dialog2-footer-actions">
|
||||||
|
<button id="dialog-close-button" class="aui-button aui-button_close">Close</button>
|
||||||
|
<button id="dialog-submit-button" class="aui-button aui-button_submit">Submit</button>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback for Rate Feedback
|
||||||
|
*
|
||||||
|
* @param Feedback
|
||||||
|
*/
|
||||||
|
let onLoadRateFunction = function (Feedback) {
|
||||||
|
$('#stars > a').each((index, el) => {
|
||||||
|
el.onmouseover = function(){
|
||||||
|
toggleStars(index);
|
||||||
|
};
|
||||||
|
el.onmouseleave = function(){
|
||||||
|
toggleStars(Feedback.feedbackScore - 1);
|
||||||
|
};
|
||||||
|
el.onclick = function(){
|
||||||
|
Feedback.feedbackScore = index + 1;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Init stars to correspond to previously entered feedback.
|
||||||
|
if (Feedback.feedbackScore > 0) {
|
||||||
|
toggleStars(Feedback.feedbackScore - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Feedback.feedbackText && Feedback.feedbackText.length > 0)
|
||||||
|
$('#feedbackTextArea').text(Feedback.feedbackText);
|
||||||
|
|
||||||
|
let submitBtn = Feedback.$el.find('#dialog-submit-button');
|
||||||
|
let closeBtn = Feedback.$el.find('#dialog-close-button');
|
||||||
|
|
||||||
|
if (submitBtn && submitBtn.length) {
|
||||||
|
submitBtn.on('click', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
Feedback.onFeedbackSubmitted();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (closeBtn && closeBtn.length) {
|
||||||
|
closeBtn.on('click', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
Feedback.hide();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#feedbackTextArea').focus();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class Dialog
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export default class Dialog {
|
||||||
|
|
||||||
|
constructor(options) {
|
||||||
|
this.feedbackScore = -1;
|
||||||
|
this.feedbackText = null;
|
||||||
|
this.submitted = false;
|
||||||
|
this.onCloseCallback = null;
|
||||||
|
|
||||||
|
this.states = {
|
||||||
|
rate_feedback: {
|
||||||
|
getHtml: createRateFeedbackHTML,
|
||||||
|
onLoad: onLoadRateFunction
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.state = options.state || 'rate_feedback';
|
||||||
|
|
||||||
|
this.window = AJS.dialog2(selector, {
|
||||||
|
closeOnOutsideClick: true
|
||||||
|
});
|
||||||
|
this.$el = this.window.$el;
|
||||||
|
|
||||||
|
AJS.dialog2(selector).on("hide", function() {
|
||||||
|
if (this.onCloseCallback) {
|
||||||
|
this.onCloseCallback();
|
||||||
|
this.onCloseCallback = null;
|
||||||
|
}
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
|
this.setState();
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(state) {
|
||||||
|
let newState = state || this.state;
|
||||||
|
|
||||||
|
let htmlStr = this.states[newState].getHtml(this);
|
||||||
|
|
||||||
|
this.$el.html(htmlStr);
|
||||||
|
|
||||||
|
this.states[newState].onLoad(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
show(cb) {
|
||||||
|
this.setState('rate_feedback');
|
||||||
|
if (typeof cb == 'function') {
|
||||||
|
this.onCloseCallback = cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.window.show();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
hide() {
|
||||||
|
this.window.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
onFeedbackSubmitted() {
|
||||||
|
let message = this.$el.find('textarea').val();
|
||||||
|
let self = this;
|
||||||
|
|
||||||
|
if (message && message.length > 0) {
|
||||||
|
self.feedbackText = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
APP.conference.sendFeedback(self.feedbackScore,
|
||||||
|
self.feedbackText);
|
||||||
|
|
||||||
|
// TO DO: make sendFeedback return true or false.
|
||||||
|
self.submitted = true;
|
||||||
|
|
||||||
|
this.hide();
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,7 +17,7 @@
|
||||||
import UIEvents from "../../../service/UI/UIEvents";
|
import UIEvents from "../../../service/UI/UIEvents";
|
||||||
import UIUtil from '../util/UIUtil';
|
import UIUtil from '../util/UIUtil';
|
||||||
import VideoLayout from '../videolayout/VideoLayout';
|
import VideoLayout from '../videolayout/VideoLayout';
|
||||||
import Feedback from '../Feedback.js';
|
import Feedback from '../feedback/Feedback.js';
|
||||||
import Toolbar from '../toolbars/Toolbar';
|
import Toolbar from '../toolbars/Toolbar';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -270,6 +270,9 @@ var Recording = {
|
||||||
initRecordingButton(recordingType) {
|
initRecordingButton(recordingType) {
|
||||||
let selector = $('#toolbar_button_record');
|
let selector = $('#toolbar_button_record');
|
||||||
|
|
||||||
|
let button = selector.get(0);
|
||||||
|
UIUtil.setTooltip(button, 'liveStreaming.buttonTooltip', 'right');
|
||||||
|
|
||||||
if (recordingType === 'jibri') {
|
if (recordingType === 'jibri') {
|
||||||
this.baseClass = "fa fa-play-circle";
|
this.baseClass = "fa fa-play-circle";
|
||||||
this.recordingTitle = "dialog.liveStreaming";
|
this.recordingTitle = "dialog.liveStreaming";
|
||||||
|
|
|
@ -1,5 +1,21 @@
|
||||||
/* global $ */
|
/* global $, APP */
|
||||||
/* jshint -W101 */
|
/* jshint -W101 */
|
||||||
|
import UIEvents from "../../../service/UI/UIEvents";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store the current ring overlay instance.
|
||||||
|
* Note: We want to have only 1 instance at a time.
|
||||||
|
*/
|
||||||
|
let overlay = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler for UIEvents.LARGE_VIDEO_AVATAR_DISPLAYED event.
|
||||||
|
* @param {boolean} shown indicates whether the avatar on the large video is
|
||||||
|
* currently displayed or not.
|
||||||
|
*/
|
||||||
|
function onAvatarDisplayed(shown) {
|
||||||
|
overlay._changeBackground(shown);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows ring overlay
|
* Shows ring overlay
|
||||||
|
@ -11,28 +27,49 @@ class RingOverlay {
|
||||||
constructor(callee) {
|
constructor(callee) {
|
||||||
this._containerId = 'ringOverlay';
|
this._containerId = 'ringOverlay';
|
||||||
this._audioContainerId = 'ringOverlayRinging';
|
this._audioContainerId = 'ringOverlayRinging';
|
||||||
|
this.isRinging = true;
|
||||||
this.callee = callee;
|
this.callee = callee;
|
||||||
this.render();
|
this.render();
|
||||||
this.audio = document.getElementById(this._audioContainerId);
|
this.audio = document.getElementById(this._audioContainerId);
|
||||||
this.audio.play();
|
this.audio.play();
|
||||||
this._setAudioTimeout();
|
this._setAudioTimeout();
|
||||||
|
this._timeout = setTimeout(() => {
|
||||||
|
this.destroy();
|
||||||
|
this.render();
|
||||||
|
}, 30000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Chagnes the background of the ring overlay.
|
||||||
|
* @param {boolean} solid - if true the new background will be the solid
|
||||||
|
* one, otherwise the background will be default one.
|
||||||
|
* NOTE: The method just toggles solidBG css class.
|
||||||
|
*/
|
||||||
|
_changeBackground(solid) {
|
||||||
|
const container = $("#" + this._containerId);
|
||||||
|
if(solid) {
|
||||||
|
container.addClass("solidBG");
|
||||||
|
} else {
|
||||||
|
container.removeClass("solidBG");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds and appends the ring overlay to the html document
|
* Builds and appends the ring overlay to the html document
|
||||||
*/
|
*/
|
||||||
_getHtmlStr(callee) {
|
_getHtmlStr(callee) {
|
||||||
|
let callingLabel = this.isRinging? "<p>Calling...</p>" : "";
|
||||||
|
let callerStateLabel = this.isRinging? "" : " isn't available";
|
||||||
return `
|
return `
|
||||||
<div id="${this._containerId}" class='ringing' >
|
<div id="${this._containerId}" class='ringing' >
|
||||||
<div class='ringing__content'>
|
<div class='ringing__content'>
|
||||||
<p>Calling...</p>
|
${callingLabel}
|
||||||
<img class='ringing__avatar' src="${callee.getAvatarUrl()}" />
|
<img class='ringing__avatar' src="${callee.getAvatarUrl()}" />
|
||||||
<div class="ringing__caller-info">
|
<div class="ringing__caller-info">
|
||||||
<p>${callee.getName()}</p>
|
<p>${callee.getName()}${callerStateLabel}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<audio id="${this._audioContainerId}" src="/sounds/ring.ogg" />
|
<audio id="${this._audioContainerId}" src="./sounds/ring.ogg" />
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,10 +86,7 @@ class RingOverlay {
|
||||||
* related to the ring overlay.
|
* related to the ring overlay.
|
||||||
*/
|
*/
|
||||||
destroy() {
|
destroy() {
|
||||||
if (this.interval) {
|
this._stopAudio();
|
||||||
clearInterval(this.interval);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._detach();
|
this._detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,6 +98,16 @@ class RingOverlay {
|
||||||
$(`#${this._containerId}`).remove();
|
$(`#${this._containerId}`).remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_stopAudio() {
|
||||||
|
this.isRinging = false;
|
||||||
|
if (this.interval) {
|
||||||
|
clearInterval(this.interval);
|
||||||
|
}
|
||||||
|
if(this._timeout) {
|
||||||
|
clearTimeout(this._timeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the interval that is going to play the ringing sound.
|
* Sets the interval that is going to play the ringing sound.
|
||||||
*/
|
*/
|
||||||
|
@ -74,12 +118,6 @@ class RingOverlay {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Store the current ring overlay instance.
|
|
||||||
* Note: We want to have only 1 instance at a time.
|
|
||||||
*/
|
|
||||||
let overlay = null;
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
/**
|
/**
|
||||||
* Shows the ring overlay for the passed callee.
|
* Shows the ring overlay for the passed callee.
|
||||||
|
@ -92,6 +130,8 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
overlay = new RingOverlay(callee);
|
overlay = new RingOverlay(callee);
|
||||||
|
APP.UI.addListener(UIEvents.LARGE_VIDEO_AVATAR_DISPLAYED,
|
||||||
|
onAvatarDisplayed);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -104,6 +144,8 @@ export default {
|
||||||
}
|
}
|
||||||
overlay.destroy();
|
overlay.destroy();
|
||||||
overlay = null;
|
overlay = null;
|
||||||
|
APP.UI.removeListener(UIEvents.LARGE_VIDEO_AVATAR_DISPLAYED,
|
||||||
|
onAvatarDisplayed);
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -243,7 +243,7 @@ export default class SharedVideoManager {
|
||||||
|
|
||||||
let thumb = new SharedVideoThumb(self.url);
|
let thumb = new SharedVideoThumb(self.url);
|
||||||
thumb.setDisplayName(player.getVideoData().title);
|
thumb.setDisplayName(player.getVideoData().title);
|
||||||
VideoLayout.addParticipantContainer(self.url, thumb);
|
VideoLayout.addRemoteVideoContainer(self.url, thumb);
|
||||||
|
|
||||||
let iframe = player.getIframe();
|
let iframe = player.getIframe();
|
||||||
self.sharedVideo = new SharedVideoContainer(
|
self.sharedVideo = new SharedVideoContainer(
|
||||||
|
@ -567,10 +567,6 @@ class SharedVideoContainer extends LargeContainer {
|
||||||
this.player = player;
|
this.player = player;
|
||||||
}
|
}
|
||||||
|
|
||||||
get $video () {
|
|
||||||
return this.$iframe;
|
|
||||||
}
|
|
||||||
|
|
||||||
show () {
|
show () {
|
||||||
let self = this;
|
let self = this;
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
|
|
|
@ -3,24 +3,26 @@
|
||||||
import {processReplacements, linkify} from './Replacement';
|
import {processReplacements, linkify} from './Replacement';
|
||||||
import CommandsProcessor from './Commands';
|
import CommandsProcessor from './Commands';
|
||||||
import ToolbarToggler from '../../toolbars/ToolbarToggler';
|
import ToolbarToggler from '../../toolbars/ToolbarToggler';
|
||||||
|
import VideoLayout from "../../videolayout/VideoLayout";
|
||||||
|
|
||||||
import UIUtil from '../../util/UIUtil';
|
import UIUtil from '../../util/UIUtil';
|
||||||
import UIEvents from '../../../../service/UI/UIEvents';
|
import UIEvents from '../../../../service/UI/UIEvents';
|
||||||
|
|
||||||
var smileys = require("./smileys.json").smileys;
|
import { smileys } from './smileys';
|
||||||
|
|
||||||
var notificationInterval = false;
|
|
||||||
var unreadMessages = 0;
|
var unreadMessages = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The container id, which is and the element id.
|
||||||
|
*/
|
||||||
|
var CHAT_CONTAINER_ID = "chat_container";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows/hides a visual notification, indicating that a message has arrived.
|
* Updates visual notification, indicating that a message has arrived.
|
||||||
*/
|
*/
|
||||||
function setVisualNotification(show) {
|
function updateVisualNotification() {
|
||||||
var unreadMsgElement = document.getElementById('unreadMessages');
|
var unreadMsgElement = document.getElementById('unreadMessages');
|
||||||
|
|
||||||
var glower = $('#toolbar_button_chat');
|
|
||||||
|
|
||||||
if (unreadMessages) {
|
if (unreadMessages) {
|
||||||
unreadMsgElement.innerHTML = unreadMessages.toString();
|
unreadMsgElement.innerHTML = unreadMessages.toString();
|
||||||
|
|
||||||
|
@ -37,28 +39,12 @@ function setVisualNotification(show) {
|
||||||
'style',
|
'style',
|
||||||
'top:' + topIndent +
|
'top:' + topIndent +
|
||||||
'; left:' + leftIndent + ';');
|
'; left:' + leftIndent + ';');
|
||||||
|
|
||||||
if (!glower.hasClass('icon-chat-simple')) {
|
|
||||||
glower.removeClass('icon-chat');
|
|
||||||
glower.addClass('icon-chat-simple');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
unreadMsgElement.innerHTML = '';
|
unreadMsgElement.innerHTML = '';
|
||||||
glower.removeClass('icon-chat-simple');
|
|
||||||
glower.addClass('icon-chat');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (show && !notificationInterval) {
|
$(unreadMsgElement).parent()[unreadMessages > 0 ? 'show' : 'hide']();
|
||||||
notificationInterval = window.setInterval(function () {
|
|
||||||
glower.toggleClass('active');
|
|
||||||
}, 800);
|
|
||||||
}
|
|
||||||
else if (!show && notificationInterval) {
|
|
||||||
window.clearInterval(notificationInterval);
|
|
||||||
notificationInterval = false;
|
|
||||||
glower.removeClass('active');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -131,7 +117,7 @@ function addSmileys() {
|
||||||
*/
|
*/
|
||||||
function resizeChatConversation() {
|
function resizeChatConversation() {
|
||||||
var msgareaHeight = $('#usermsg').outerHeight();
|
var msgareaHeight = $('#usermsg').outerHeight();
|
||||||
var chatspace = $('#chat_container');
|
var chatspace = $('#' + CHAT_CONTAINER_ID);
|
||||||
var width = chatspace.width();
|
var width = chatspace.width();
|
||||||
var chat = $('#chatconversation');
|
var chat = $('#chatconversation');
|
||||||
var smileys = $('#smileysarea');
|
var smileys = $('#smileysarea');
|
||||||
|
@ -187,13 +173,30 @@ var Chat = {
|
||||||
};
|
};
|
||||||
usermsg.autosize({callback: onTextAreaResize});
|
usermsg.autosize({callback: onTextAreaResize});
|
||||||
|
|
||||||
$("#chat_container").bind("shown",
|
eventEmitter.on(UIEvents.SIDE_TOOLBAR_CONTAINER_TOGGLED,
|
||||||
function () {
|
function(containerId, isVisible) {
|
||||||
|
if (containerId !== CHAT_CONTAINER_ID || !isVisible)
|
||||||
|
return;
|
||||||
|
|
||||||
unreadMessages = 0;
|
unreadMessages = 0;
|
||||||
setVisualNotification(false);
|
updateVisualNotification();
|
||||||
|
|
||||||
|
// Undock the toolbar when the chat is shown and if we're in a
|
||||||
|
// video mode.
|
||||||
|
if (VideoLayout.isLargeVideoVisible()) {
|
||||||
|
ToolbarToggler.dockToolbar(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we are in conversation mode focus on the text input
|
||||||
|
// if we are not, focus on the display name input
|
||||||
|
if (APP.settings.getDisplayName())
|
||||||
|
$('#usermsg').focus();
|
||||||
|
else
|
||||||
|
$('#nickinput').focus();
|
||||||
});
|
});
|
||||||
|
|
||||||
addSmileys();
|
addSmileys();
|
||||||
|
updateVisualNotification();
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -210,7 +213,7 @@ var Chat = {
|
||||||
if (!Chat.isVisible()) {
|
if (!Chat.isVisible()) {
|
||||||
unreadMessages++;
|
unreadMessages++;
|
||||||
UIUtil.playSoundNotification('chatNotification');
|
UIUtil.playSoundNotification('chatNotification');
|
||||||
setVisualNotification(true);
|
updateVisualNotification();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -271,12 +274,18 @@ var Chat = {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the chat conversation mode.
|
* Sets the chat conversation mode.
|
||||||
|
* Conversation mode is the normal chat mode, non conversation mode is
|
||||||
|
* where we ask user to input its display name.
|
||||||
* @param {boolean} isConversationMode if chat should be in
|
* @param {boolean} isConversationMode if chat should be in
|
||||||
* conversation mode or not.
|
* conversation mode or not.
|
||||||
*/
|
*/
|
||||||
setChatConversationMode (isConversationMode) {
|
setChatConversationMode (isConversationMode) {
|
||||||
$('#chat_container')
|
$('#' + CHAT_CONTAINER_ID)
|
||||||
.toggleClass('is-conversation-mode', isConversationMode);
|
.toggleClass('is-conversation-mode', isConversationMode);
|
||||||
|
|
||||||
|
// this is needed when we transition from no conversation mode to
|
||||||
|
// conversation mode. When user enters his nickname and hits enter,
|
||||||
|
// to focus on the write area.
|
||||||
if (isConversationMode) {
|
if (isConversationMode) {
|
||||||
$('#usermsg').focus();
|
$('#usermsg').focus();
|
||||||
}
|
}
|
||||||
|
@ -286,7 +295,7 @@ var Chat = {
|
||||||
* Resizes the chat area.
|
* Resizes the chat area.
|
||||||
*/
|
*/
|
||||||
resizeChat (width, height) {
|
resizeChat (width, height) {
|
||||||
$('#chat_container').width(width).height(height);
|
$('#' + CHAT_CONTAINER_ID).width(width).height(height);
|
||||||
|
|
||||||
resizeChatConversation();
|
resizeChatConversation();
|
||||||
},
|
},
|
||||||
|
@ -296,7 +305,7 @@ var Chat = {
|
||||||
*/
|
*/
|
||||||
isVisible () {
|
isVisible () {
|
||||||
return UIUtil.isVisible(
|
return UIUtil.isVisible(
|
||||||
document.getElementById("chat_container"));
|
document.getElementById(CHAT_CONTAINER_ID));
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Shows and hides the window with the smileys
|
* Shows and hides the window with the smileys
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/* jshint -W101 */
|
/* jshint -W101 */
|
||||||
var Smileys = require("./smileys.json");
|
import { regexes } from './smileys';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processes links and smileys in "body"
|
* Processes links and smileys in "body"
|
||||||
|
@ -29,7 +29,7 @@ export function linkify(inputText) {
|
||||||
replacePattern2 = /(^|[^\/])(www\.[\S]+(\b|$))/gim;
|
replacePattern2 = /(^|[^\/])(www\.[\S]+(\b|$))/gim;
|
||||||
replacedText = replacedText.replace(replacePattern2, '$1<a href="http://$2" target="_blank">$2</a>');
|
replacedText = replacedText.replace(replacePattern2, '$1<a href="http://$2" target="_blank">$2</a>');
|
||||||
|
|
||||||
//Change email addresses to mailto:: links.
|
//Change email addresses to mailto: links.
|
||||||
replacePattern3 = /(([a-zA-Z0-9\-\_\.])+@[a-zA-Z\_]+?(\.[a-zA-Z]{2,6})+)/gim;
|
replacePattern3 = /(([a-zA-Z0-9\-\_\.])+@[a-zA-Z\_]+?(\.[a-zA-Z]{2,6})+)/gim;
|
||||||
replacedText = replacedText.replace(replacePattern3, '<a href="mailto:$1">$1</a>');
|
replacedText = replacedText.replace(replacePattern3, '<a href="mailto:$1">$1</a>');
|
||||||
|
|
||||||
|
@ -44,10 +44,9 @@ function smilify(body) {
|
||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
|
|
||||||
var regexs = Smileys.regexs;
|
for(var smiley in regexes) {
|
||||||
for(var smiley in regexs) {
|
if(regexes.hasOwnProperty(smiley)) {
|
||||||
if(regexs.hasOwnProperty(smiley)) {
|
body = body.replace(regexes[smiley],
|
||||||
body = body.replace(regexs[smiley],
|
|
||||||
'<img class="smiley" src="images/smileys/' + smiley + '.svg">');
|
'<img class="smiley" src="images/smileys/' + smiley + '.svg">');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
export const smileys = {
|
||||||
|
smiley1: ":)",
|
||||||
|
smiley2: ":(",
|
||||||
|
smiley3: ":D",
|
||||||
|
smiley4: "(y)",
|
||||||
|
smiley5: " :P",
|
||||||
|
smiley6: "(wave)",
|
||||||
|
smiley7: "(blush)",
|
||||||
|
smiley8: "(chuckle)",
|
||||||
|
smiley9: "(shocked)",
|
||||||
|
smiley10: ":*",
|
||||||
|
smiley11: "(n)",
|
||||||
|
smiley12: "(search)",
|
||||||
|
smiley13: " <3",
|
||||||
|
smiley14: "(oops)",
|
||||||
|
smiley15: "(angry)",
|
||||||
|
smiley16: "(angel)",
|
||||||
|
smiley17: "(sick)",
|
||||||
|
smiley18: ";(",
|
||||||
|
smiley19: "(bomb)",
|
||||||
|
smiley20: "(clap)",
|
||||||
|
smiley21: " ;)"
|
||||||
|
};
|
||||||
|
|
||||||
|
export const regexes = {
|
||||||
|
smiley2: /(:-\(\(|:-\(|:\(\(|:\(|\(sad\))/gi,
|
||||||
|
smiley3: /(:-\)\)|:\)\)|\(lol\)|:-D|:D)/gi,
|
||||||
|
smiley1: /(:-\)|:\))/gi,
|
||||||
|
smiley4: /(\(y\)|\(Y\)|\(ok\))/gi,
|
||||||
|
smiley5: /(:-P|:P|:-p|:p)/gi,
|
||||||
|
smiley6: /(\(wave\))/gi,
|
||||||
|
smiley7: /(\(blush\))/gi,
|
||||||
|
smiley8: /(\(chuckle\))/gi,
|
||||||
|
smiley9: /(:-0|\(shocked\))/gi,
|
||||||
|
smiley10: /(:-\*|:\*|\(kiss\))/gi,
|
||||||
|
smiley11: /(\(n\))/gi,
|
||||||
|
smiley12: /(\(search\))/g,
|
||||||
|
smiley13: /(<3|<3|&lt;3|\(L\)|\(l\)|\(H\)|\(h\))/gi,
|
||||||
|
smiley14: /(\(oops\))/gi,
|
||||||
|
smiley15: /(\(angry\))/gi,
|
||||||
|
smiley16: /(\(angel\))/gi,
|
||||||
|
smiley17: /(\(sick\))/gi,
|
||||||
|
smiley18: /(;-\(\(|;\(\(|;-\(|;\(|:"\(|:"-\(|:~-\(|:~\(|\(upset\))/gi,
|
||||||
|
smiley19: /(\(bomb\))/gi,
|
||||||
|
smiley20: /(\(clap\))/gi,
|
||||||
|
smiley21: /(;-\)|;\)|;-\)\)|;\)\)|;-D|;D|\(wink\))/gi
|
||||||
|
};
|
|
@ -1,48 +0,0 @@
|
||||||
{
|
|
||||||
"smileys": {
|
|
||||||
"smiley1": ":)",
|
|
||||||
"smiley2": ":(",
|
|
||||||
"smiley3": ":D",
|
|
||||||
"smiley4": "(y)",
|
|
||||||
"smiley5": " :P",
|
|
||||||
"smiley6": "(wave)",
|
|
||||||
"smiley7": "(blush)",
|
|
||||||
"smiley8": "(chuckle)",
|
|
||||||
"smiley9": "(shocked)",
|
|
||||||
"smiley10": ":*",
|
|
||||||
"smiley11": "(n)",
|
|
||||||
"smiley12": "(search)",
|
|
||||||
"smiley13": " <3",
|
|
||||||
"smiley14": "(oops)",
|
|
||||||
"smiley15": "(angry)",
|
|
||||||
"smiley16": "(angel)",
|
|
||||||
"smiley17": "(sick)",
|
|
||||||
"smiley18": ";(",
|
|
||||||
"smiley19": "(bomb)",
|
|
||||||
"smiley20": "(clap)",
|
|
||||||
"smiley21": " ;)"
|
|
||||||
},
|
|
||||||
"regexs": {
|
|
||||||
"smiley2": /(:-\(\(|:-\(|:\(\(|:\(|\(sad\))/gi,
|
|
||||||
"smiley3": /(:-\)\)|:\)\)|\(lol\)|:-D|:D)/gi,
|
|
||||||
"smiley1": /(:-\)|:\))/gi,
|
|
||||||
"smiley4": /(\(y\)|\(Y\)|\(ok\))/gi,
|
|
||||||
"smiley5": /(:-P|:P|:-p|:p)/gi,
|
|
||||||
"smiley6": /(\(wave\))/gi,
|
|
||||||
"smiley7": /(\(blush\))/gi,
|
|
||||||
"smiley8": /(\(chuckle\))/gi,
|
|
||||||
"smiley9": /(:-0|\(shocked\))/gi,
|
|
||||||
"smiley10": /(:-\*|:\*|\(kiss\))/gi,
|
|
||||||
"smiley11": /(\(n\))/gi,
|
|
||||||
"smiley12": /(\(search\))/g,
|
|
||||||
"smiley13": /(<3|<3|&lt;3|\(L\)|\(l\)|\(H\)|\(h\))/gi,
|
|
||||||
"smiley14": /(\(oops\))/gi,
|
|
||||||
"smiley15": /(\(angry\))/gi,
|
|
||||||
"smiley16": /(\(angel\))/gi,
|
|
||||||
"smiley17": /(\(sick\))/gi,
|
|
||||||
"smiley18": /(;-\(\(|;\(\(|;-\(|;\(|:"\(|:"-\(|:~-\(|:~\(|\(upset\))/gi,
|
|
||||||
"smiley19": /(\(bomb\))/gi,
|
|
||||||
"smiley20": /(\(clap\))/gi,
|
|
||||||
"smiley21": /(;-\)|;\)|;-\)\)|;\)\)|;-D|;D|\(wink\))/gi
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -20,13 +20,11 @@ function updateNumberOfParticipants(delta) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let buttonIndicatorText = (numberOfContacts === 1) ? '' : numberOfContacts;
|
$("#numberOfParticipants").text(numberOfContacts);
|
||||||
$("#numberOfParticipants").text(buttonIndicatorText);
|
|
||||||
|
|
||||||
$("#contacts_container>div.title").text(
|
$("#contacts_container>div.title").text(
|
||||||
APP.translation.translateString(
|
APP.translation.translateString("contactlist")
|
||||||
"contactlist", {participants: numberOfContacts}
|
+ ' (' + numberOfContacts + ')');
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -59,16 +57,6 @@ function createDisplayNameParagraph(key, displayName) {
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function stopGlowing(glower) {
|
|
||||||
window.clearInterval(notificationInterval);
|
|
||||||
notificationInterval = false;
|
|
||||||
glower.removeClass('glowing');
|
|
||||||
if (!ContactList.isVisible()) {
|
|
||||||
glower.removeClass('active');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getContactEl (id) {
|
function getContactEl (id) {
|
||||||
return $(`#contacts>li[id="${id}"]`);
|
return $(`#contacts>li[id="${id}"]`);
|
||||||
}
|
}
|
||||||
|
@ -96,9 +84,9 @@ var ContactList = {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a contact for the given id.
|
* Adds a contact for the given id.
|
||||||
*
|
* @param isLocal is an id for the local user.
|
||||||
*/
|
*/
|
||||||
addContact (id) {
|
addContact (id, isLocal) {
|
||||||
let contactlist = $('#contacts');
|
let contactlist = $('#contacts');
|
||||||
|
|
||||||
let newContact = document.createElement('li');
|
let newContact = document.createElement('li');
|
||||||
|
@ -112,7 +100,11 @@ var ContactList = {
|
||||||
|
|
||||||
if (interfaceConfig.SHOW_CONTACTLIST_AVATARS)
|
if (interfaceConfig.SHOW_CONTACTLIST_AVATARS)
|
||||||
newContact.appendChild(createAvatar(id));
|
newContact.appendChild(createAvatar(id));
|
||||||
newContact.appendChild(createDisplayNameParagraph("participant"));
|
|
||||||
|
newContact.appendChild(
|
||||||
|
createDisplayNameParagraph(
|
||||||
|
isLocal ? interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME : null,
|
||||||
|
isLocal ? null : interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME));
|
||||||
|
|
||||||
if (APP.conference.isLocalId(id)) {
|
if (APP.conference.isLocalId(id)) {
|
||||||
contactlist.prepend(newContact);
|
contactlist.prepend(newContact);
|
||||||
|
|
|
@ -74,10 +74,7 @@ export default {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Only show the subtitle if this isn't the only setting section.
|
UIUtil.showElement("deviceOptionsTitle");
|
||||||
if (interfaceConfig.SETTINGS_SECTIONS.length > 1)
|
|
||||||
UIUtil.showElement("deviceOptionsTitle");
|
|
||||||
|
|
||||||
UIUtil.showElement("devicesOptions");
|
UIUtil.showElement("devicesOptions");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,8 +147,7 @@ export default {
|
||||||
showStartMutedOptions (show) {
|
showStartMutedOptions (show) {
|
||||||
if (show && UIUtil.isSettingEnabled('moderator')) {
|
if (show && UIUtil.isSettingEnabled('moderator')) {
|
||||||
// Only show the subtitle if this isn't the only setting section.
|
// Only show the subtitle if this isn't the only setting section.
|
||||||
if (!$("#moderatorOptionsTitle").is(":visible")
|
if (!$("#moderatorOptionsTitle").is(":visible"))
|
||||||
&& interfaceConfig.SETTINGS_SECTIONS.length > 1)
|
|
||||||
UIUtil.showElement("moderatorOptionsTitle");
|
UIUtil.showElement("moderatorOptionsTitle");
|
||||||
|
|
||||||
UIUtil.showElement("startMutedOptions");
|
UIUtil.showElement("startMutedOptions");
|
||||||
|
|
|
@ -7,7 +7,6 @@ import SideContainerToggler from "../side_pannels/SideContainerToggler";
|
||||||
let roomUrl = null;
|
let roomUrl = null;
|
||||||
let emitter = null;
|
let emitter = null;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens the invite link dialog.
|
* Opens the invite link dialog.
|
||||||
*/
|
*/
|
||||||
|
@ -21,36 +20,46 @@ function openLinkDialog () {
|
||||||
inviteAttributes = "value=\"" + encodeURI(roomUrl) + "\"";
|
inviteAttributes = "value=\"" + encodeURI(roomUrl) + "\"";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let inviteLinkId = "inviteLinkRef";
|
||||||
|
let focusInviteLink = function() {
|
||||||
|
$('#' + inviteLinkId).focus();
|
||||||
|
$('#' + inviteLinkId).select();
|
||||||
|
};
|
||||||
|
|
||||||
let title = APP.translation.generateTranslationHTML("dialog.shareLink");
|
let title = APP.translation.generateTranslationHTML("dialog.shareLink");
|
||||||
APP.UI.messageHandler.openTwoButtonDialog(
|
APP.UI.messageHandler.openTwoButtonDialog(
|
||||||
null, null, null,
|
null, title, null,
|
||||||
'<h2>' + title + '</h2>'
|
'<input id="' + inviteLinkId + '" type="text" '
|
||||||
+ '<input id="inviteLinkRef" type="text" '
|
+ inviteAttributes + ' readonly/>',
|
||||||
+ inviteAttributes + ' onclick="this.select();" readonly>',
|
false, "dialog.copy",
|
||||||
false, "dialog.Invite",
|
|
||||||
function (e, v) {
|
function (e, v) {
|
||||||
if (v && roomUrl) {
|
if (v && roomUrl) {
|
||||||
JitsiMeetJS.analytics.sendEvent('toolbar.invite.button');
|
JitsiMeetJS.analytics.sendEvent('toolbar.invite.button');
|
||||||
emitter.emit(UIEvents.USER_INVITED, roomUrl);
|
|
||||||
|
focusInviteLink();
|
||||||
|
|
||||||
|
document.execCommand('copy');
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
JitsiMeetJS.analytics.sendEvent('toolbar.invite.cancel');
|
JitsiMeetJS.analytics.sendEvent('toolbar.invite.cancel');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
function (event) {
|
function (event) {
|
||||||
if (roomUrl) {
|
if (!roomUrl) {
|
||||||
document.getElementById('inviteLinkRef').select();
|
|
||||||
} else {
|
|
||||||
if (event && event.target) {
|
if (event && event.target) {
|
||||||
$(event.target).find('button[value=true]')
|
$(event.target).find('button[value=true]')
|
||||||
.prop('disabled', true);
|
.prop('disabled', true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
focusInviteLink();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
function (e, v, m, f) {
|
function (e, v, m, f) {
|
||||||
if(!v && !m && !f)
|
if(!v && !m && !f)
|
||||||
JitsiMeetJS.analytics.sendEvent('toolbar.invite.close');
|
JitsiMeetJS.analytics.sendEvent('toolbar.invite.close');
|
||||||
}
|
},
|
||||||
|
'Copy' // Focus Copy button.
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,6 +190,7 @@ const buttonHandlers = {
|
||||||
const defaultToolbarButtons = {
|
const defaultToolbarButtons = {
|
||||||
'microphone': {
|
'microphone': {
|
||||||
id: 'toolbar_button_mute',
|
id: 'toolbar_button_mute',
|
||||||
|
tooltipKey: 'toolbar.mute',
|
||||||
className: "button icon-microphone",
|
className: "button icon-microphone",
|
||||||
shortcut: 'M',
|
shortcut: 'M',
|
||||||
shortcutAttr: 'mutePopover',
|
shortcutAttr: 'mutePopover',
|
||||||
|
@ -211,6 +221,7 @@ const defaultToolbarButtons = {
|
||||||
},
|
},
|
||||||
'camera': {
|
'camera': {
|
||||||
id: 'toolbar_button_camera',
|
id: 'toolbar_button_camera',
|
||||||
|
tooltipKey: 'toolbar.videomute',
|
||||||
className: "button icon-camera",
|
className: "button icon-camera",
|
||||||
shortcut: 'V',
|
shortcut: 'V',
|
||||||
shortcutAttr: 'toggleVideoPopover',
|
shortcutAttr: 'toggleVideoPopover',
|
||||||
|
@ -224,6 +235,7 @@ const defaultToolbarButtons = {
|
||||||
},
|
},
|
||||||
'desktop': {
|
'desktop': {
|
||||||
id: 'toolbar_button_desktopsharing',
|
id: 'toolbar_button_desktopsharing',
|
||||||
|
tooltipKey: 'toolbar.sharescreen',
|
||||||
className: 'button icon-share-desktop',
|
className: 'button icon-share-desktop',
|
||||||
shortcut: 'D',
|
shortcut: 'D',
|
||||||
shortcutAttr: 'toggleDesktopSharingPopover',
|
shortcutAttr: 'toggleDesktopSharingPopover',
|
||||||
|
@ -236,16 +248,19 @@ const defaultToolbarButtons = {
|
||||||
i18n: '[content]toolbar.sharescreen'
|
i18n: '[content]toolbar.sharescreen'
|
||||||
},
|
},
|
||||||
'security': {
|
'security': {
|
||||||
id: 'toolbar_button_security'
|
id: 'toolbar_button_security',
|
||||||
|
tooltipKey: 'toolbar.lock'
|
||||||
},
|
},
|
||||||
'invite': {
|
'invite': {
|
||||||
id: 'toolbar_button_link',
|
id: 'toolbar_button_link',
|
||||||
|
tooltipKey: 'toolbar.invite',
|
||||||
className: 'button icon-link',
|
className: 'button icon-link',
|
||||||
content: 'Invite others',
|
content: 'Invite others',
|
||||||
i18n: '[content]toolbar.invite'
|
i18n: '[content]toolbar.invite'
|
||||||
},
|
},
|
||||||
'chat': {
|
'chat': {
|
||||||
id: 'toolbar_button_chat',
|
id: 'toolbar_button_chat',
|
||||||
|
tooltipKey: 'toolbar.chat',
|
||||||
shortcut: 'C',
|
shortcut: 'C',
|
||||||
shortcutAttr: 'toggleChatPopover',
|
shortcutAttr: 'toggleChatPopover',
|
||||||
shortcutFunc: function() {
|
shortcutFunc: function() {
|
||||||
|
@ -257,40 +272,47 @@ const defaultToolbarButtons = {
|
||||||
},
|
},
|
||||||
'contacts': {
|
'contacts': {
|
||||||
id: 'toolbar_contact_list',
|
id: 'toolbar_contact_list',
|
||||||
|
tooltipKey: 'bottomtoolbar.contactlist',
|
||||||
sideContainerId: 'contacts_container'
|
sideContainerId: 'contacts_container'
|
||||||
},
|
},
|
||||||
'profile': {
|
'profile': {
|
||||||
id: 'toolbar_button_profile',
|
id: 'toolbar_button_profile',
|
||||||
|
tooltipKey: 'profile.setDisplayNameLabel',
|
||||||
sideContainerId: 'profile_container'
|
sideContainerId: 'profile_container'
|
||||||
},
|
},
|
||||||
'etherpad': {
|
'etherpad': {
|
||||||
id: 'toolbar_button_etherpad'
|
id: 'toolbar_button_etherpad',
|
||||||
|
tooltipKey: 'toolbar.etherpad',
|
||||||
},
|
},
|
||||||
'fullscreen': {
|
'fullscreen': {
|
||||||
id: 'toolbar_button_fullScreen',
|
id: 'toolbar_button_fullScreen',
|
||||||
|
tooltipKey: 'toolbar.fullscreen',
|
||||||
className: "button icon-full-screen",
|
className: "button icon-full-screen",
|
||||||
shortcut: 'F',
|
shortcut: 'S',
|
||||||
shortcutAttr: 'toggleFullscreenPopover',
|
shortcutAttr: 'toggleFullscreenPopover',
|
||||||
shortcutFunc: function() {
|
shortcutFunc: function() {
|
||||||
JitsiMeetJS.analytics.sendEvent('shortcut.fullscreen.toggled');
|
JitsiMeetJS.analytics.sendEvent('shortcut.fullscreen.toggled');
|
||||||
APP.UI.toggleFullScreen();
|
APP.UI.toggleFullScreen();
|
||||||
},
|
},
|
||||||
shortcutDescription: "keyboardShortcuts.toggleChat",
|
shortcutDescription: "keyboardShortcuts.fullScreen",
|
||||||
content: "Enter / Exit Full Screen",
|
content: "Enter / Exit Full Screen",
|
||||||
i18n: "[content]toolbar.fullscreen"
|
i18n: "[content]toolbar.fullscreen"
|
||||||
},
|
},
|
||||||
'settings': {
|
'settings': {
|
||||||
id: 'toolbar_button_settings',
|
id: 'toolbar_button_settings',
|
||||||
|
tooltipKey: 'toolbar.Settings',
|
||||||
sideContainerId: "settings_container"
|
sideContainerId: "settings_container"
|
||||||
},
|
},
|
||||||
'hangup': {
|
'hangup': {
|
||||||
id: 'toolbar_button_hangup',
|
id: 'toolbar_button_hangup',
|
||||||
|
tooltipKey: 'toolbar.hangup',
|
||||||
className: "button icon-hangup",
|
className: "button icon-hangup",
|
||||||
content: "Hang Up",
|
content: "Hang Up",
|
||||||
i18n: "[content]toolbar.hangup"
|
i18n: "[content]toolbar.hangup"
|
||||||
},
|
},
|
||||||
'filmstrip': {
|
'filmstrip': {
|
||||||
id: 'toolbar_film_strip',
|
id: 'toolbar_film_strip',
|
||||||
|
tooltipKey: 'toolbar.filmstrip',
|
||||||
shortcut: "F",
|
shortcut: "F",
|
||||||
shortcutAttr: "filmstripPopover",
|
shortcutAttr: "filmstripPopover",
|
||||||
shortcutFunc: function() {
|
shortcutFunc: function() {
|
||||||
|
@ -301,6 +323,7 @@ const defaultToolbarButtons = {
|
||||||
},
|
},
|
||||||
'raisehand': {
|
'raisehand': {
|
||||||
id: "toolbar_button_raisehand",
|
id: "toolbar_button_raisehand",
|
||||||
|
tooltipKey: 'toolbar.raiseHand',
|
||||||
className: "button icon-raised-hand",
|
className: "button icon-raised-hand",
|
||||||
shortcut: "R",
|
shortcut: "R",
|
||||||
shortcutAttr: "raiseHandPopover",
|
shortcutAttr: "raiseHandPopover",
|
||||||
|
@ -357,7 +380,17 @@ const Toolbar = {
|
||||||
Object.keys(defaultToolbarButtons).forEach(
|
Object.keys(defaultToolbarButtons).forEach(
|
||||||
id => {
|
id => {
|
||||||
if (UIUtil.isButtonEnabled(id)) {
|
if (UIUtil.isButtonEnabled(id)) {
|
||||||
var button = defaultToolbarButtons[id];
|
let button = defaultToolbarButtons[id];
|
||||||
|
let buttonElement = document.getElementById(button.id);
|
||||||
|
|
||||||
|
let tooltipPosition
|
||||||
|
= (interfaceConfig.MAIN_TOOLBAR_BUTTONS
|
||||||
|
.indexOf(id) > -1)
|
||||||
|
? "bottom" : "right";
|
||||||
|
|
||||||
|
UIUtil.setTooltip( buttonElement,
|
||||||
|
button.tooltipKey,
|
||||||
|
tooltipPosition);
|
||||||
|
|
||||||
if (button.shortcut)
|
if (button.shortcut)
|
||||||
APP.keyboardshortcut.registerShortcut(
|
APP.keyboardshortcut.registerShortcut(
|
||||||
|
@ -382,8 +415,15 @@ const Toolbar = {
|
||||||
isVisible);
|
isVisible);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
APP.UI.addListener(UIEvents.LOCAL_RAISE_HAND_CHANGED,
|
||||||
|
function(isRaisedHand) {
|
||||||
|
Toolbar._toggleRaiseHand(isRaisedHand);
|
||||||
|
});
|
||||||
|
|
||||||
if(!APP.tokenData.isGuest) {
|
if(!APP.tokenData.isGuest) {
|
||||||
$("#toolbar_button_profile").addClass("unclickable");
|
$("#toolbar_button_profile").addClass("unclickable");
|
||||||
|
UIUtil.removeTooltip(
|
||||||
|
document.getElementById('toolbar_button_profile'));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
|
@ -458,9 +498,11 @@ const Toolbar = {
|
||||||
|
|
||||||
// Shows or hides the 'shared video' button.
|
// Shows or hides the 'shared video' button.
|
||||||
showSharedVideoButton () {
|
showSharedVideoButton () {
|
||||||
|
let $element = $('#toolbar_button_sharedvideo');
|
||||||
if (UIUtil.isButtonEnabled('sharedvideo')
|
if (UIUtil.isButtonEnabled('sharedvideo')
|
||||||
&& config.disableThirdPartyRequests !== true) {
|
&& config.disableThirdPartyRequests !== true) {
|
||||||
$('#toolbar_button_sharedvideo').css({display: "inline-block"});
|
$element.css({display: "inline-block"});
|
||||||
|
UIUtil.setTooltip($element.get(0), 'toolbar.sharedvideo', 'right');
|
||||||
} else {
|
} else {
|
||||||
$('#toolbar_button_sharedvideo').css({display: "none"});
|
$('#toolbar_button_sharedvideo').css({display: "none"});
|
||||||
}
|
}
|
||||||
|
@ -545,6 +587,13 @@ const Toolbar = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles / untoggles the view for raised hand.
|
||||||
|
*/
|
||||||
|
_toggleRaiseHand(isRaisedHand) {
|
||||||
|
$('#toolbar_button_raisehand').toggleClass("glow", isRaisedHand);
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Marks video icon as muted or not.
|
* Marks video icon as muted or not.
|
||||||
* @param {boolean} muted if icon should look like muted or not
|
* @param {boolean} muted if icon should look like muted or not
|
||||||
|
@ -750,7 +799,6 @@ const Toolbar = {
|
||||||
buttonElement.setAttribute("data-i18n", button.i18n);
|
buttonElement.setAttribute("data-i18n", button.i18n);
|
||||||
|
|
||||||
buttonElement.setAttribute("data-container", "body");
|
buttonElement.setAttribute("data-container", "body");
|
||||||
buttonElement.setAttribute("data-toggle", "popover");
|
|
||||||
buttonElement.setAttribute("data-placement", "bottom");
|
buttonElement.setAttribute("data-placement", "bottom");
|
||||||
this._addPopups(buttonElement, button.popups);
|
this._addPopups(buttonElement, button.popups);
|
||||||
|
|
||||||
|
@ -771,4 +819,4 @@ const Toolbar = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Toolbar;
|
export default Toolbar;
|
|
@ -1,4 +1,22 @@
|
||||||
/* global $, config, interfaceConfig */
|
/* global $, APP, config, AJS, interfaceConfig */
|
||||||
|
|
||||||
|
import KeyboardShortcut from '../../keyboardshortcut/keyboardshortcut';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Associates tooltip element position (in the terms of
|
||||||
|
* {@link UIUtil#setTooltip} which do not look like CSS <tt>position</tt>) with
|
||||||
|
* AUI tooltip <tt>gravity</tt>.
|
||||||
|
*/
|
||||||
|
const TOOLTIP_POSITIONS = {
|
||||||
|
'bottom': 'n',
|
||||||
|
'bottom-left': 'ne',
|
||||||
|
'bottom-right': 'nw',
|
||||||
|
'left': 'e',
|
||||||
|
'right': 'w',
|
||||||
|
'top': 's',
|
||||||
|
'top-left': 'se',
|
||||||
|
'top-right': 'sw'
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by hristo on 12/22/14.
|
* Created by hristo on 12/22/14.
|
||||||
|
@ -82,12 +100,71 @@
|
||||||
context.putImageData(imgData, 0, 0);
|
context.putImageData(imgData, 0, 0);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a global handler for all tooltips. Once invoked, create a new
|
||||||
|
* tooltip by merely updating a DOM node with the appropriate class (e.g.
|
||||||
|
* <tt>tooltip-n</tt>) and the attribute <tt>content</tt>.
|
||||||
|
*/
|
||||||
|
activateTooltips() {
|
||||||
|
AJS.$('[data-tooltip]').tooltip({
|
||||||
|
gravity() {
|
||||||
|
return this.getAttribute('data-tooltip');
|
||||||
|
},
|
||||||
|
|
||||||
|
title() {
|
||||||
|
return this.getAttribute('content');
|
||||||
|
},
|
||||||
|
|
||||||
|
html: true, // Handle multiline tooltips.
|
||||||
|
|
||||||
|
// The following two prevent tooltips from being stuck:
|
||||||
|
hoverable: false, // Make custom tooltips behave like native ones.
|
||||||
|
live: true // Attach listener to document element.
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the tooltip to the given element.
|
||||||
|
*
|
||||||
|
* @param element the element to set the tooltip to
|
||||||
|
* @param key the tooltip data-i18n key
|
||||||
|
* @param position the position of the tooltip in relation to the element
|
||||||
|
*/
|
||||||
setTooltip: function (element, key, position) {
|
setTooltip: function (element, key, position) {
|
||||||
element.setAttribute("data-i18n", "[data-content]" + key);
|
element.setAttribute('data-tooltip', TOOLTIP_POSITIONS[position]);
|
||||||
element.setAttribute("data-toggle", "popover");
|
element.setAttribute('data-i18n', '[content]' + key);
|
||||||
element.setAttribute("data-placement", position);
|
|
||||||
element.setAttribute("data-html", true);
|
APP.translation.translateElement($(element));
|
||||||
element.setAttribute("data-container", "body");
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the tooltip to the given element.
|
||||||
|
*
|
||||||
|
* @param element the element to remove the tooltip from
|
||||||
|
*/
|
||||||
|
removeTooltip: function (element) {
|
||||||
|
AJS.$(element).tooltip('destroy');
|
||||||
|
element.setAttribute('data-tooltip', '');
|
||||||
|
element.setAttribute('data-i18n','');
|
||||||
|
element.setAttribute('content','');
|
||||||
|
element.setAttribute('shortcut','');
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal util function for generating tooltip title.
|
||||||
|
*
|
||||||
|
* @param element
|
||||||
|
* @returns {string|*}
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_getTooltipText: function (element) {
|
||||||
|
let title = element.getAttribute('content');
|
||||||
|
let shortcut = element.getAttribute('shortcut');
|
||||||
|
if(shortcut) {
|
||||||
|
let shortcutString = KeyboardShortcut.getShortcutTooltip(shortcut);
|
||||||
|
title += ` ${shortcutString}`;
|
||||||
|
}
|
||||||
|
return title;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -233,6 +310,23 @@
|
||||||
*/
|
*/
|
||||||
parseCssInt(cssValue) {
|
parseCssInt(cssValue) {
|
||||||
return parseInt(cssValue) || 0;
|
return parseInt(cssValue) || 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds href value to 'a' link jquery object. If link value is null,
|
||||||
|
* undefined or empty string, disables the link.
|
||||||
|
* @param {object} aLinkElement the jquery object
|
||||||
|
* @param {string} link the link value
|
||||||
|
*/
|
||||||
|
setLinkHref(aLinkElement, link) {
|
||||||
|
if (link) {
|
||||||
|
aLinkElement.attr('href', link);
|
||||||
|
} else {
|
||||||
|
aLinkElement.css({
|
||||||
|
"pointer-events": "none",
|
||||||
|
"cursor": "default"
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -245,13 +245,13 @@ ConnectionIndicator.prototype.showMore = function () {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
function createIcon(classes) {
|
function createIcon(classes, iconClass) {
|
||||||
var icon = document.createElement("span");
|
var icon = document.createElement("span");
|
||||||
for(var i in classes) {
|
for(var i in classes) {
|
||||||
icon.classList.add(classes[i]);
|
icon.classList.add(classes[i]);
|
||||||
}
|
}
|
||||||
icon.appendChild(
|
icon.appendChild(
|
||||||
document.createElement("i")).classList.add("icon-connection");
|
document.createElement("i")).classList.add(iconClass);
|
||||||
return icon;
|
return icon;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -282,9 +282,12 @@ ConnectionIndicator.prototype.create = function () {
|
||||||
}.bind(this);
|
}.bind(this);
|
||||||
|
|
||||||
this.emptyIcon = this.connectionIndicatorContainer.appendChild(
|
this.emptyIcon = this.connectionIndicatorContainer.appendChild(
|
||||||
createIcon(["connection", "connection_empty"]));
|
createIcon(["connection", "connection_empty"], "icon-connection"));
|
||||||
this.fullIcon = this.connectionIndicatorContainer.appendChild(
|
this.fullIcon = this.connectionIndicatorContainer.appendChild(
|
||||||
createIcon(["connection", "connection_full"]));
|
createIcon(["connection", "connection_full"], "icon-connection"));
|
||||||
|
this.interruptedIndicator = this.connectionIndicatorContainer.appendChild(
|
||||||
|
createIcon(["connection", "connection_lost"],"icon-connection-lost"));
|
||||||
|
$(this.interruptedIndicator).hide();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -298,6 +301,27 @@ ConnectionIndicator.prototype.remove = function() {
|
||||||
this.popover.forceHide();
|
this.popover.forceHide();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the UI which displays warning about user's connectivity problems.
|
||||||
|
*
|
||||||
|
* @param {boolean} isActive true if the connection is working fine or false if
|
||||||
|
* the user is having connectivity issues.
|
||||||
|
*/
|
||||||
|
ConnectionIndicator.prototype.updateConnectionStatusIndicator
|
||||||
|
= function (isActive) {
|
||||||
|
this.isConnectionActive = isActive;
|
||||||
|
if (this.isConnectionActive) {
|
||||||
|
$(this.interruptedIndicator).hide();
|
||||||
|
$(this.emptyIcon).show();
|
||||||
|
$(this.fullIcon).show();
|
||||||
|
} else {
|
||||||
|
$(this.interruptedIndicator).show();
|
||||||
|
$(this.emptyIcon).hide();
|
||||||
|
$(this.fullIcon).hide();
|
||||||
|
this.updateConnectionQuality(0 /* zero bars */);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the data of the indicator
|
* Updates the data of the indicator
|
||||||
* @param percent the percent of connection quality
|
* @param percent the percent of connection quality
|
||||||
|
@ -312,15 +336,16 @@ ConnectionIndicator.prototype.updateConnectionQuality =
|
||||||
} else {
|
} else {
|
||||||
if(this.connectionIndicatorContainer.style.display == "none") {
|
if(this.connectionIndicatorContainer.style.display == "none") {
|
||||||
this.connectionIndicatorContainer.style.display = "block";
|
this.connectionIndicatorContainer.style.display = "block";
|
||||||
this.videoContainer.updateIconPositions();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.bandwidth = object.bandwidth;
|
if (object) {
|
||||||
this.bitrate = object.bitrate;
|
this.bandwidth = object.bandwidth;
|
||||||
this.packetLoss = object.packetLoss;
|
this.bitrate = object.bitrate;
|
||||||
this.transport = object.transport;
|
this.packetLoss = object.packetLoss;
|
||||||
if (object.resolution) {
|
this.transport = object.transport;
|
||||||
this.resolution = object.resolution;
|
if (object.resolution) {
|
||||||
|
this.resolution = object.resolution;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for (var quality in ConnectionIndicator.connectionQualityValues) {
|
for (var quality in ConnectionIndicator.connectionQualityValues) {
|
||||||
if (percent >= quality) {
|
if (percent >= quality) {
|
||||||
|
@ -328,7 +353,7 @@ ConnectionIndicator.prototype.updateConnectionQuality =
|
||||||
ConnectionIndicator.connectionQualityValues[quality];
|
ConnectionIndicator.connectionQualityValues[quality];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (object.isResolutionHD) {
|
if (object && typeof object.isResolutionHD === 'boolean') {
|
||||||
this.isResolutionHD = object.isResolutionHD;
|
this.isResolutionHD = object.isResolutionHD;
|
||||||
}
|
}
|
||||||
this.updateResolutionIndicator();
|
this.updateResolutionIndicator();
|
||||||
|
|
|
@ -3,8 +3,6 @@
|
||||||
import UIEvents from "../../../service/UI/UIEvents";
|
import UIEvents from "../../../service/UI/UIEvents";
|
||||||
import UIUtil from "../util/UIUtil";
|
import UIUtil from "../util/UIUtil";
|
||||||
|
|
||||||
const thumbAspectRatio = 1 / 1;
|
|
||||||
|
|
||||||
const FilmStrip = {
|
const FilmStrip = {
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
@ -26,7 +24,7 @@ const FilmStrip = {
|
||||||
*/
|
*/
|
||||||
toggleFilmStrip (visible) {
|
toggleFilmStrip (visible) {
|
||||||
if (typeof visible === 'boolean'
|
if (typeof visible === 'boolean'
|
||||||
&& this.isFilmStripVisible() == visible) {
|
&& this.isFilmStripVisible() == visible) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,8 +34,8 @@ const FilmStrip = {
|
||||||
var eventEmitter = this.eventEmitter;
|
var eventEmitter = this.eventEmitter;
|
||||||
if (eventEmitter) {
|
if (eventEmitter) {
|
||||||
eventEmitter.emit(
|
eventEmitter.emit(
|
||||||
UIEvents.TOGGLED_FILM_STRIP,
|
UIEvents.TOGGLED_FILM_STRIP,
|
||||||
this.isFilmStripVisible());
|
this.isFilmStripVisible());
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -66,13 +64,52 @@ const FilmStrip = {
|
||||||
- parseInt(this.filmStrip.css('paddingRight'), 10);
|
- parseInt(this.filmStrip.css('paddingRight'), 10);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
calculateThumbnailSize() {
|
||||||
* Calculates the thumbnail size.
|
let availableSizes = this.calculateAvailableSize();
|
||||||
*/
|
let width = availableSizes.availableWidth;
|
||||||
calculateThumbnailSize () {
|
let height = availableSizes.availableHeight;
|
||||||
let availableHeight = interfaceConfig.FILM_STRIP_MAX_HEIGHT;
|
|
||||||
|
|
||||||
let numvids = this.getThumbs(true).length;
|
return this.calculateThumbnailSizeFromAvailable(width, height);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalizes local and remote thumbnail ratios
|
||||||
|
*/
|
||||||
|
normalizeThumbnailRatio () {
|
||||||
|
let remoteHeightRatio = interfaceConfig.REMOTE_THUMBNAIL_RATIO_HEIGHT;
|
||||||
|
let remoteWidthRatio = interfaceConfig.REMOTE_THUMBNAIL_RATIO_WIDTH;
|
||||||
|
|
||||||
|
let localHeightRatio = interfaceConfig.LOCAL_THUMBNAIL_RATIO_HEIGHT;
|
||||||
|
let localWidthRatio = interfaceConfig.LOCAL_THUMBNAIL_RATIO_WIDTH;
|
||||||
|
|
||||||
|
let commonHeightRatio = remoteHeightRatio * localHeightRatio;
|
||||||
|
|
||||||
|
let localRatioCoefficient = localWidthRatio / localHeightRatio;
|
||||||
|
let remoteRatioCoefficient = remoteWidthRatio / remoteHeightRatio;
|
||||||
|
|
||||||
|
remoteWidthRatio = commonHeightRatio * remoteRatioCoefficient;
|
||||||
|
remoteHeightRatio = commonHeightRatio;
|
||||||
|
|
||||||
|
localWidthRatio = commonHeightRatio * localRatioCoefficient;
|
||||||
|
localHeightRatio = commonHeightRatio;
|
||||||
|
|
||||||
|
let localRatio = {
|
||||||
|
widthRatio: localWidthRatio,
|
||||||
|
heightRatio: localHeightRatio
|
||||||
|
};
|
||||||
|
|
||||||
|
let remoteRatio = {
|
||||||
|
widthRatio: remoteWidthRatio,
|
||||||
|
heightRatio: remoteHeightRatio
|
||||||
|
};
|
||||||
|
|
||||||
|
return { localRatio, remoteRatio };
|
||||||
|
},
|
||||||
|
|
||||||
|
calculateAvailableSize() {
|
||||||
|
let availableHeight = interfaceConfig.FILM_STRIP_MAX_HEIGHT;
|
||||||
|
let thumbs = this.getThumbs(true);
|
||||||
|
let numvids = thumbs.remoteThumbs.length;
|
||||||
|
|
||||||
let localVideoContainer = $("#localVideoContainer");
|
let localVideoContainer = $("#localVideoContainer");
|
||||||
|
|
||||||
|
@ -83,20 +120,19 @@ const FilmStrip = {
|
||||||
*/
|
*/
|
||||||
let videoAreaAvailableWidth
|
let videoAreaAvailableWidth
|
||||||
= UIUtil.getAvailableVideoWidth()
|
= UIUtil.getAvailableVideoWidth()
|
||||||
- UIUtil.parseCssInt(this.filmStrip.css('right'), 10)
|
- UIUtil.parseCssInt(this.filmStrip.css('right'), 10)
|
||||||
- UIUtil.parseCssInt(this.filmStrip.css('paddingLeft'), 10)
|
- UIUtil.parseCssInt(this.filmStrip.css('paddingLeft'), 10)
|
||||||
- UIUtil.parseCssInt(this.filmStrip.css('paddingRight'), 10)
|
- UIUtil.parseCssInt(this.filmStrip.css('paddingRight'), 10)
|
||||||
- UIUtil.parseCssInt(this.filmStrip.css('borderLeftWidth'), 10)
|
- UIUtil.parseCssInt(this.filmStrip.css('borderLeftWidth'), 10)
|
||||||
- UIUtil.parseCssInt(this.filmStrip.css('borderRightWidth'), 10)
|
- UIUtil.parseCssInt(this.filmStrip.css('borderRightWidth'), 10)
|
||||||
- 5;
|
- 5;
|
||||||
|
|
||||||
let availableWidth = videoAreaAvailableWidth;
|
let availableWidth = videoAreaAvailableWidth;
|
||||||
|
|
||||||
// If the number of videos is 0 or undefined we don't need to calculate
|
// If local thumb is not hidden
|
||||||
// further.
|
if(thumbs.localThumb) {
|
||||||
if (numvids)
|
|
||||||
availableWidth = Math.floor(
|
availableWidth = Math.floor(
|
||||||
(videoAreaAvailableWidth - numvids * (
|
(videoAreaAvailableWidth - (
|
||||||
UIUtil.parseCssInt(
|
UIUtil.parseCssInt(
|
||||||
localVideoContainer.css('borderLeftWidth'), 10)
|
localVideoContainer.css('borderLeftWidth'), 10)
|
||||||
+ UIUtil.parseCssInt(
|
+ UIUtil.parseCssInt(
|
||||||
|
@ -109,45 +145,99 @@ const FilmStrip = {
|
||||||
localVideoContainer.css('marginLeft'), 10)
|
localVideoContainer.css('marginLeft'), 10)
|
||||||
+ UIUtil.parseCssInt(
|
+ UIUtil.parseCssInt(
|
||||||
localVideoContainer.css('marginRight'), 10)))
|
localVideoContainer.css('marginRight'), 10)))
|
||||||
/ numvids);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the number of videos is 0 or undefined we don't need to calculate
|
||||||
|
// further.
|
||||||
|
if (numvids) {
|
||||||
|
let remoteVideoContainer = thumbs.remoteThumbs.eq(0);
|
||||||
|
availableWidth = Math.floor(
|
||||||
|
(videoAreaAvailableWidth - numvids * (
|
||||||
|
UIUtil.parseCssInt(
|
||||||
|
remoteVideoContainer.css('borderLeftWidth'), 10)
|
||||||
|
+ UIUtil.parseCssInt(
|
||||||
|
remoteVideoContainer.css('borderRightWidth'), 10)
|
||||||
|
+ UIUtil.parseCssInt(
|
||||||
|
remoteVideoContainer.css('paddingLeft'), 10)
|
||||||
|
+ UIUtil.parseCssInt(
|
||||||
|
remoteVideoContainer.css('paddingRight'), 10)
|
||||||
|
+ UIUtil.parseCssInt(
|
||||||
|
remoteVideoContainer.css('marginLeft'), 10)
|
||||||
|
+ UIUtil.parseCssInt(
|
||||||
|
remoteVideoContainer.css('marginRight'), 10)))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
let maxHeight
|
let maxHeight
|
||||||
// If the MAX_HEIGHT property hasn't been specified
|
// If the MAX_HEIGHT property hasn't been specified
|
||||||
// we have the static value.
|
// we have the static value.
|
||||||
= Math.min( interfaceConfig.FILM_STRIP_MAX_HEIGHT || 120,
|
= Math.min(interfaceConfig.FILM_STRIP_MAX_HEIGHT || 120,
|
||||||
availableHeight);
|
availableHeight);
|
||||||
|
|
||||||
availableHeight
|
availableHeight
|
||||||
= Math.min( maxHeight, window.innerHeight - 18);
|
= Math.min(maxHeight, window.innerHeight - 18);
|
||||||
|
|
||||||
if (availableHeight < availableWidth) {
|
return { availableWidth, availableHeight };
|
||||||
availableWidth = availableHeight;
|
},
|
||||||
|
|
||||||
|
calculateThumbnailSizeFromAvailable(availableWidth, availableHeight) {
|
||||||
|
let { localRatio, remoteRatio } = this.normalizeThumbnailRatio();
|
||||||
|
let { remoteThumbs } = this.getThumbs(true);
|
||||||
|
let remoteProportion = remoteRatio.widthRatio * remoteThumbs.length;
|
||||||
|
let widthProportion = remoteProportion + localRatio.widthRatio;
|
||||||
|
|
||||||
|
let heightUnit = availableHeight / localRatio.heightRatio;
|
||||||
|
let widthUnit = availableWidth / widthProportion;
|
||||||
|
|
||||||
|
if (heightUnit < widthUnit) {
|
||||||
|
widthUnit = heightUnit;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
availableHeight = availableWidth;
|
heightUnit = widthUnit;
|
||||||
|
|
||||||
|
let localVideo = {
|
||||||
|
thumbWidth: widthUnit * localRatio.widthRatio,
|
||||||
|
thumbHeight: heightUnit * localRatio.heightRatio
|
||||||
|
};
|
||||||
|
let remoteVideo = {
|
||||||
|
thumbWidth: widthUnit * remoteRatio.widthRatio,
|
||||||
|
thumbHeight: widthUnit * remoteRatio.heightRatio
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
thumbWidth: availableWidth,
|
localVideo,
|
||||||
thumbHeight: availableHeight
|
remoteVideo
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
resizeThumbnails (thumbWidth, thumbHeight,
|
resizeThumbnails (local, remote,
|
||||||
animate = false, forceUpdate = false) {
|
animate = false, forceUpdate = false) {
|
||||||
|
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
this.getThumbs(!forceUpdate).animate({
|
let thumbs = this.getThumbs(!forceUpdate);
|
||||||
height: thumbHeight,
|
if(thumbs.localThumb)
|
||||||
width: thumbWidth
|
thumbs.localThumb.animate({
|
||||||
}, {
|
height: local.thumbHeight,
|
||||||
queue: false,
|
width: local.thumbWidth
|
||||||
duration: animate ? 500 : 0,
|
}, {
|
||||||
complete: resolve
|
queue: false,
|
||||||
});
|
duration: animate ? 500 : 0,
|
||||||
|
complete: resolve
|
||||||
|
});
|
||||||
|
if(thumbs.remoteThumbs)
|
||||||
|
thumbs.remoteThumbs.animate({
|
||||||
|
height: remote.thumbHeight,
|
||||||
|
width: remote.thumbWidth
|
||||||
|
}, {
|
||||||
|
queue: false,
|
||||||
|
duration: animate ? 500 : 0,
|
||||||
|
complete: resolve
|
||||||
|
});
|
||||||
|
|
||||||
this.filmStrip.animate({
|
this.filmStrip.animate({
|
||||||
// adds 2 px because of small video 1px border
|
// adds 2 px because of small video 1px border
|
||||||
height: thumbHeight + 2
|
height: remote.thumbHeight + 2
|
||||||
}, {
|
}, {
|
||||||
queue: false,
|
queue: false,
|
||||||
duration: animate ? 500 : 0
|
duration: animate ? 500 : 0
|
||||||
|
@ -165,13 +255,19 @@ const FilmStrip = {
|
||||||
selector += ':visible';
|
selector += ':visible';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let localThumb = $("#localVideoContainer");
|
||||||
|
let remoteThumbs = this.filmStrip.children(selector)
|
||||||
|
.not("#localVideoContainer");
|
||||||
|
|
||||||
// Exclude the local video container if it has been hidden.
|
// Exclude the local video container if it has been hidden.
|
||||||
if ($("#localVideoContainer").hasClass("hidden"))
|
if (localThumb.hasClass("hidden")) {
|
||||||
return this.filmStrip.children(selector)
|
return { remoteThumbs };
|
||||||
.not("#localVideoContainer");
|
} else {
|
||||||
else
|
return { remoteThumbs, localThumb };
|
||||||
return this.filmStrip.children(selector);
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default FilmStrip;
|
export default FilmStrip;
|
||||||
|
|
|
@ -0,0 +1,501 @@
|
||||||
|
/* global $, APP, interfaceConfig */
|
||||||
|
/* jshint -W101 */
|
||||||
|
|
||||||
|
import Avatar from "../avatar/Avatar";
|
||||||
|
import {createDeferred} from '../../util/helpers';
|
||||||
|
import UIUtil from "../util/UIUtil";
|
||||||
|
import {VideoContainer, VIDEO_CONTAINER_TYPE} from "./VideoContainer";
|
||||||
|
|
||||||
|
import LargeContainer from "./LargeContainer";
|
||||||
|
|
||||||
|
import AudioLevels from "../audio_levels/AudioLevels";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manager for all Large containers.
|
||||||
|
*/
|
||||||
|
export default class LargeVideoManager {
|
||||||
|
constructor (emitter) {
|
||||||
|
/**
|
||||||
|
* The map of <tt>LargeContainer</tt>s where the key is the video
|
||||||
|
* container type.
|
||||||
|
* @type {Object.<string, LargeContainer>}
|
||||||
|
*/
|
||||||
|
this.containers = {};
|
||||||
|
|
||||||
|
this.state = VIDEO_CONTAINER_TYPE;
|
||||||
|
this.videoContainer = new VideoContainer(
|
||||||
|
() => this.resizeContainer(VIDEO_CONTAINER_TYPE), emitter);
|
||||||
|
this.addContainer(VIDEO_CONTAINER_TYPE, this.videoContainer);
|
||||||
|
|
||||||
|
// use the same video container to handle and desktop tracks
|
||||||
|
this.addContainer("desktop", this.videoContainer);
|
||||||
|
|
||||||
|
this.width = 0;
|
||||||
|
this.height = 0;
|
||||||
|
|
||||||
|
this.$container = $('#largeVideoContainer');
|
||||||
|
|
||||||
|
this.$container.css({
|
||||||
|
display: 'inline-block'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (interfaceConfig.SHOW_JITSI_WATERMARK) {
|
||||||
|
let leftWatermarkDiv
|
||||||
|
= this.$container.find("div.watermark.leftwatermark");
|
||||||
|
|
||||||
|
leftWatermarkDiv.css({display: 'block'});
|
||||||
|
|
||||||
|
UIUtil.setLinkHref(
|
||||||
|
leftWatermarkDiv.parent(),
|
||||||
|
interfaceConfig.JITSI_WATERMARK_LINK);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (interfaceConfig.SHOW_BRAND_WATERMARK) {
|
||||||
|
let rightWatermarkDiv
|
||||||
|
= this.$container.find("div.watermark.rightwatermark");
|
||||||
|
|
||||||
|
rightWatermarkDiv.css({
|
||||||
|
display: 'block',
|
||||||
|
backgroundImage: 'url(images/rightwatermark.png)'
|
||||||
|
});
|
||||||
|
|
||||||
|
UIUtil.setLinkHref(
|
||||||
|
rightWatermarkDiv.parent(),
|
||||||
|
interfaceConfig.BRAND_WATERMARK_LINK);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (interfaceConfig.SHOW_POWERED_BY) {
|
||||||
|
this.$container.children("a.poweredby").css({display: 'block'});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$container.hover(
|
||||||
|
e => this.onHoverIn(e),
|
||||||
|
e => this.onHoverOut(e)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
onHoverIn (e) {
|
||||||
|
if (!this.state) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let container = this.getContainer(this.state);
|
||||||
|
container.onHoverIn(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
onHoverOut (e) {
|
||||||
|
if (!this.state) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let container = this.getContainer(this.state);
|
||||||
|
container.onHoverOut(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the media connection has been interrupted.
|
||||||
|
*/
|
||||||
|
onVideoInterrupted () {
|
||||||
|
this.enableLocalConnectionProblemFilter(true);
|
||||||
|
this._setLocalConnectionMessage("connection.RECONNECTING");
|
||||||
|
// Show the message only if the video is currently being displayed
|
||||||
|
this.showLocalConnectionMessage(this.state === VIDEO_CONTAINER_TYPE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the media connection has been restored.
|
||||||
|
*/
|
||||||
|
onVideoRestored () {
|
||||||
|
this.enableLocalConnectionProblemFilter(false);
|
||||||
|
this.showLocalConnectionMessage(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
get id () {
|
||||||
|
let container = this.getContainer(this.state);
|
||||||
|
return container.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
scheduleLargeVideoUpdate () {
|
||||||
|
if (this.updateInProcess || !this.newStreamData) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateInProcess = true;
|
||||||
|
|
||||||
|
let container = this.getContainer(this.state);
|
||||||
|
|
||||||
|
// Include hide()/fadeOut only if we're switching between users
|
||||||
|
let preUpdate;
|
||||||
|
let isUserSwitch = this.newStreamData.id != this.id;
|
||||||
|
if (isUserSwitch) {
|
||||||
|
preUpdate = container.hide();
|
||||||
|
} else {
|
||||||
|
preUpdate = Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
preUpdate.then(() => {
|
||||||
|
let {id, stream, videoType, resolve} = this.newStreamData;
|
||||||
|
this.newStreamData = null;
|
||||||
|
|
||||||
|
console.info("hover in %s", id);
|
||||||
|
this.state = videoType;
|
||||||
|
let container = this.getContainer(this.state);
|
||||||
|
container.setStream(stream, videoType);
|
||||||
|
|
||||||
|
// change the avatar url on large
|
||||||
|
this.updateAvatar(Avatar.getAvatarUrl(id));
|
||||||
|
|
||||||
|
// FIXME that does not really make sense, because the videoType
|
||||||
|
// (camera or desktop) is a completely different thing than
|
||||||
|
// the video container type (Etherpad, SharedVideo, VideoContainer).
|
||||||
|
// ----------------------------------------------------------------
|
||||||
|
// If we the continer is VIDEO_CONTAINER_TYPE, we need to check
|
||||||
|
// its stream whether exist and is muted to set isVideoMuted
|
||||||
|
// in rest of the cases it is false
|
||||||
|
let showAvatar = false;
|
||||||
|
if (videoType == VIDEO_CONTAINER_TYPE)
|
||||||
|
showAvatar = stream ? stream.isMuted() : true;
|
||||||
|
|
||||||
|
// If the user's connection is disrupted then the avatar will be
|
||||||
|
// displayed in case we have no video image cached. That is if
|
||||||
|
// there was a user switch(image is lost on stream detach) or if
|
||||||
|
// the video was not rendered, before the connection has failed.
|
||||||
|
let isHavingConnectivityIssues
|
||||||
|
= APP.conference.isParticipantConnectionActive(id) === false;
|
||||||
|
if (isHavingConnectivityIssues
|
||||||
|
&& (isUserSwitch | !container.wasVideoRendered)) {
|
||||||
|
showAvatar = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let promise;
|
||||||
|
|
||||||
|
// do not show stream if video is muted
|
||||||
|
// but we still should show watermark
|
||||||
|
if (showAvatar) {
|
||||||
|
this.showWatermark(true);
|
||||||
|
// If the intention of this switch is to show the avatar
|
||||||
|
// we need to make sure that the video is hidden
|
||||||
|
promise = container.hide();
|
||||||
|
} else {
|
||||||
|
promise = container.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
// show the avatar on large if needed
|
||||||
|
container.showAvatar(showAvatar);
|
||||||
|
|
||||||
|
// Make sure no notification about remote failure is shown as
|
||||||
|
// it's UI conflicts with the one for local connection interrupted.
|
||||||
|
if (APP.conference.isConnectionInterrupted()) {
|
||||||
|
this.updateParticipantConnStatusIndication(id, true);
|
||||||
|
} else {
|
||||||
|
this.updateParticipantConnStatusIndication(
|
||||||
|
id, !isHavingConnectivityIssues);
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolve updateLargeVideo promise after everything is done
|
||||||
|
promise.then(resolve);
|
||||||
|
|
||||||
|
return promise;
|
||||||
|
}).then(() => {
|
||||||
|
// after everything is done check again if there are any pending
|
||||||
|
// new streams.
|
||||||
|
this.updateInProcess = false;
|
||||||
|
this.scheduleLargeVideoUpdate();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows/hides notification about participant's connectivity issues to be
|
||||||
|
* shown on the large video area.
|
||||||
|
*
|
||||||
|
* @param {string} id the id of remote participant(MUC nickname)
|
||||||
|
* @param {boolean} isConnected true if the connection is active or false
|
||||||
|
* when the user is having connectivity issues.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
updateParticipantConnStatusIndication (id, isConnected) {
|
||||||
|
|
||||||
|
// Apply grey filter on the large video
|
||||||
|
this.videoContainer.showRemoteConnectionProblemIndicator(!isConnected);
|
||||||
|
|
||||||
|
if (isConnected) {
|
||||||
|
// Hide the message
|
||||||
|
this.showRemoteConnectionMessage(false);
|
||||||
|
} else {
|
||||||
|
// Get user's display name
|
||||||
|
let displayName
|
||||||
|
= APP.conference.getParticipantDisplayName(id);
|
||||||
|
this._setRemoteConnectionMessage(
|
||||||
|
"connection.USER_CONNECTION_INTERRUPTED",
|
||||||
|
{ displayName: displayName });
|
||||||
|
|
||||||
|
// Show it now only if the VideoContainer is on top
|
||||||
|
this.showRemoteConnectionMessage(
|
||||||
|
this.state === VIDEO_CONTAINER_TYPE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update large video.
|
||||||
|
* Switches to large video even if previously other container was visible.
|
||||||
|
* @param userID the userID of the participant associated with the stream
|
||||||
|
* @param {JitsiTrack?} stream new stream
|
||||||
|
* @param {string?} videoType new video type
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
updateLargeVideo (userID, stream, videoType) {
|
||||||
|
if (this.newStreamData) {
|
||||||
|
this.newStreamData.reject();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.newStreamData = createDeferred();
|
||||||
|
this.newStreamData.id = userID;
|
||||||
|
this.newStreamData.stream = stream;
|
||||||
|
this.newStreamData.videoType = videoType;
|
||||||
|
|
||||||
|
this.scheduleLargeVideoUpdate();
|
||||||
|
|
||||||
|
return this.newStreamData.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update container size.
|
||||||
|
*/
|
||||||
|
updateContainerSize () {
|
||||||
|
this.width = UIUtil.getAvailableVideoWidth();
|
||||||
|
this.height = window.innerHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resize Large container of specified type.
|
||||||
|
* @param {string} type type of container which should be resized.
|
||||||
|
* @param {boolean} [animate=false] if resize process should be animated.
|
||||||
|
*/
|
||||||
|
resizeContainer (type, animate = false) {
|
||||||
|
let container = this.getContainer(type);
|
||||||
|
container.resize(this.width, this.height, animate);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resize all Large containers.
|
||||||
|
* @param {boolean} animate if resize process should be animated.
|
||||||
|
*/
|
||||||
|
resize (animate) {
|
||||||
|
// resize all containers
|
||||||
|
Object.keys(this.containers)
|
||||||
|
.forEach(type => this.resizeContainer(type, animate));
|
||||||
|
|
||||||
|
this.$container.animate({
|
||||||
|
width: this.width,
|
||||||
|
height: this.height
|
||||||
|
}, {
|
||||||
|
queue: false,
|
||||||
|
duration: animate ? 500 : 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables/disables the filter indicating a video problem to the user caused
|
||||||
|
* by the problems with local media connection.
|
||||||
|
*
|
||||||
|
* @param enable <tt>true</tt> to enable, <tt>false</tt> to disable
|
||||||
|
*/
|
||||||
|
enableLocalConnectionProblemFilter (enable) {
|
||||||
|
this.videoContainer.enableLocalConnectionProblemFilter(enable);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the src of the dominant speaker avatar
|
||||||
|
*/
|
||||||
|
updateAvatar (avatarUrl) {
|
||||||
|
$("#dominantSpeakerAvatar").attr('src', avatarUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the audio level indicator of the large video.
|
||||||
|
*
|
||||||
|
* @param lvl the new audio level to set
|
||||||
|
*/
|
||||||
|
updateLargeVideoAudioLevel (lvl) {
|
||||||
|
AudioLevels.updateLargeVideoAudioLevel("dominantSpeaker", lvl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show or hide watermark.
|
||||||
|
* @param {boolean} show
|
||||||
|
*/
|
||||||
|
showWatermark (show) {
|
||||||
|
$('.watermark').css('visibility', show ? 'visible' : 'hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows/hides the message indicating problems with local media connection.
|
||||||
|
* @param {boolean|null} show(optional) tells whether the message is to be
|
||||||
|
* displayed or not. If missing the condition will be based on the value
|
||||||
|
* obtained from {@link APP.conference.isConnectionInterrupted}.
|
||||||
|
*/
|
||||||
|
showLocalConnectionMessage (show) {
|
||||||
|
if (typeof show !== 'boolean') {
|
||||||
|
show = APP.conference.isConnectionInterrupted();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (show) {
|
||||||
|
$('#localConnectionMessage').css({display: "block"});
|
||||||
|
// Avatar message conflicts with 'videoConnectionMessage',
|
||||||
|
// so it must be hidden
|
||||||
|
this.showRemoteConnectionMessage(false);
|
||||||
|
} else {
|
||||||
|
$('#localConnectionMessage').css({display: "none"});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows hides the "avatar" message which is to be displayed either in
|
||||||
|
* the middle of the screen or below the avatar image.
|
||||||
|
*
|
||||||
|
* @param {null|boolean} show (optional) <tt>true</tt> to show the avatar
|
||||||
|
* message or <tt>false</tt> to hide it. If not provided then the connection
|
||||||
|
* status of the user currently on the large video will be obtained form
|
||||||
|
* "APP.conference" and the message will be displayed if the user's
|
||||||
|
* connection is interrupted.
|
||||||
|
*/
|
||||||
|
showRemoteConnectionMessage (show) {
|
||||||
|
if (typeof show !== 'boolean') {
|
||||||
|
show = APP.conference.isParticipantConnectionActive(this.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (show) {
|
||||||
|
$('#remoteConnectionMessage').css({display: "block"});
|
||||||
|
// 'videoConnectionMessage' message conflicts with 'avatarMessage',
|
||||||
|
// so it must be hidden
|
||||||
|
this.showLocalConnectionMessage(false);
|
||||||
|
} else {
|
||||||
|
$('#remoteConnectionMessage').hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the text which describes that the remote user is having
|
||||||
|
* connectivity issues.
|
||||||
|
*
|
||||||
|
* @param {string} msgKey the translation key which will be used to get
|
||||||
|
* the message text.
|
||||||
|
* @param {object} msgOptions translation options object.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_setRemoteConnectionMessage (msgKey, msgOptions) {
|
||||||
|
if (msgKey) {
|
||||||
|
let text = APP.translation.translateString(msgKey, msgOptions);
|
||||||
|
$('#remoteConnectionMessage')
|
||||||
|
.attr("data-i18n", msgKey).text(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.videoContainer.positionRemoteConnectionMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updated the text which is to be shown on the top of large video, when
|
||||||
|
* local media connection is interrupted.
|
||||||
|
*
|
||||||
|
* @param {string} msgKey the translation key which will be used to get
|
||||||
|
* the message text to be displayed on the large video.
|
||||||
|
* @param {object} msgOptions translation options object
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_setLocalConnectionMessage (msgKey, msgOptions) {
|
||||||
|
$('#localConnectionMessage')
|
||||||
|
.attr("data-i18n", msgKey)
|
||||||
|
.text(APP.translation.translateString(msgKey, msgOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add container of specified type.
|
||||||
|
* @param {string} type container type
|
||||||
|
* @param {LargeContainer} container container to add.
|
||||||
|
*/
|
||||||
|
addContainer (type, container) {
|
||||||
|
if (this.containers[type]) {
|
||||||
|
throw new Error(`container of type ${type} already exist`);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.containers[type] = container;
|
||||||
|
this.resizeContainer(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Large container of specified type.
|
||||||
|
* @param {string} type container type.
|
||||||
|
* @returns {LargeContainer}
|
||||||
|
*/
|
||||||
|
getContainer (type) {
|
||||||
|
let container = this.containers[type];
|
||||||
|
|
||||||
|
if (!container) {
|
||||||
|
throw new Error(`container of type ${type} doesn't exist`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove Large container of specified type.
|
||||||
|
* @param {string} type container type.
|
||||||
|
*/
|
||||||
|
removeContainer (type) {
|
||||||
|
if (!this.containers[type]) {
|
||||||
|
throw new Error(`container of type ${type} doesn't exist`);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete this.containers[type];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show Large container of specified type.
|
||||||
|
* Does nothing if such container is already visible.
|
||||||
|
* @param {string} type container type.
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
showContainer (type) {
|
||||||
|
if (this.state === type) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
let oldContainer = this.containers[this.state];
|
||||||
|
// FIXME when video is being replaced with other content we need to hide
|
||||||
|
// companion icons/messages. It would be best if the container would
|
||||||
|
// be taking care of it by itself, but that is a bigger refactoring
|
||||||
|
if (this.state === VIDEO_CONTAINER_TYPE) {
|
||||||
|
this.showWatermark(false);
|
||||||
|
this.showLocalConnectionMessage(false);
|
||||||
|
this.showRemoteConnectionMessage(false);
|
||||||
|
}
|
||||||
|
oldContainer.hide();
|
||||||
|
|
||||||
|
this.state = type;
|
||||||
|
let container = this.getContainer(type);
|
||||||
|
|
||||||
|
return container.show().then(() => {
|
||||||
|
if (type === VIDEO_CONTAINER_TYPE) {
|
||||||
|
// FIXME when video appears on top of other content we need to
|
||||||
|
// show companion icons/messages. It would be best if
|
||||||
|
// the container would be taking care of it by itself, but that
|
||||||
|
// is a bigger refactoring
|
||||||
|
this.showWatermark(true);
|
||||||
|
// "avatar" and "video connection" can not be displayed both
|
||||||
|
// at the same time, but the latter is of higher priority and it
|
||||||
|
// will hide the avatar one if will be displayed.
|
||||||
|
this.showRemoteConnectionMessage(/* fet the current state */);
|
||||||
|
this.showLocalConnectionMessage(/* fetch the current state */);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the flipX state of the local video.
|
||||||
|
* @param val {boolean} true if flipped.
|
||||||
|
*/
|
||||||
|
onLocalFlipXChange(val) {
|
||||||
|
this.videoContainer.setLocalFlipX(val);
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,7 +11,6 @@ function LocalVideo(VideoLayout, emitter) {
|
||||||
this.videoSpanId = "localVideoContainer";
|
this.videoSpanId = "localVideoContainer";
|
||||||
this.container = $("#localVideoContainer").get(0);
|
this.container = $("#localVideoContainer").get(0);
|
||||||
this.localVideoId = null;
|
this.localVideoId = null;
|
||||||
this.bindHoverHandler();
|
|
||||||
if(config.enableLocalVideoFlip)
|
if(config.enableLocalVideoFlip)
|
||||||
this._buildContextMenu();
|
this._buildContextMenu();
|
||||||
this.isLocal = true;
|
this.isLocal = true;
|
||||||
|
@ -29,6 +28,7 @@ function LocalVideo(VideoLayout, emitter) {
|
||||||
this.setDisplayName();
|
this.setDisplayName();
|
||||||
|
|
||||||
this.createConnectionIndicator();
|
this.createConnectionIndicator();
|
||||||
|
this.addAudioLevelIndicator();
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalVideo.prototype = Object.create(SmallVideo.prototype);
|
LocalVideo.prototype = Object.create(SmallVideo.prototype);
|
||||||
|
@ -44,7 +44,7 @@ function createEditDisplayNameButton() {
|
||||||
editButton.className = 'displayname';
|
editButton.className = 'displayname';
|
||||||
UIUtil.setTooltip(editButton,
|
UIUtil.setTooltip(editButton,
|
||||||
"videothumbnail.editnickname",
|
"videothumbnail.editnickname",
|
||||||
"top");
|
"left");
|
||||||
editButton.innerHTML = '<i class="icon-edit"></i>';
|
editButton.innerHTML = '<i class="icon-edit"></i>';
|
||||||
|
|
||||||
return editButton;
|
return editButton;
|
||||||
|
@ -61,7 +61,7 @@ LocalVideo.prototype.setDisplayName = function(displayName, key) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var nameSpan = $('#' + this.videoSpanId + '>span.displayname');
|
var nameSpan = $('#' + this.videoSpanId + ' .displayname');
|
||||||
var defaultLocalDisplayName = APP.translation.generateTranslationHTML(
|
var defaultLocalDisplayName = APP.translation.generateTranslationHTML(
|
||||||
interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME);
|
interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME);
|
||||||
|
|
||||||
|
@ -72,7 +72,10 @@ LocalVideo.prototype.setDisplayName = function(displayName, key) {
|
||||||
if (displayName && displayName.length > 0) {
|
if (displayName && displayName.length > 0) {
|
||||||
meHTML = APP.translation.generateTranslationHTML("me");
|
meHTML = APP.translation.generateTranslationHTML("me");
|
||||||
$('#localDisplayName').html(
|
$('#localDisplayName').html(
|
||||||
UIUtil.escapeHtml(displayName) + ' (' + meHTML + ')'
|
`${UIUtil.escapeHtml(displayName)} (${meHTML})`
|
||||||
|
);
|
||||||
|
$('#editDisplayName').val(
|
||||||
|
`${UIUtil.escapeHtml(displayName)}`
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
$('#localDisplayName').html(defaultLocalDisplayName);
|
$('#localDisplayName').html(defaultLocalDisplayName);
|
||||||
|
@ -80,11 +83,11 @@ LocalVideo.prototype.setDisplayName = function(displayName, key) {
|
||||||
}
|
}
|
||||||
this.updateView();
|
this.updateView();
|
||||||
} else {
|
} else {
|
||||||
var editButton = createEditDisplayNameButton();
|
|
||||||
|
|
||||||
nameSpan = document.createElement('span');
|
nameSpan = document.createElement('span');
|
||||||
nameSpan.className = 'displayname';
|
nameSpan.className = 'displayname';
|
||||||
$('#' + this.videoSpanId)[0].appendChild(nameSpan);
|
document.getElementById(this.videoSpanId)
|
||||||
|
.querySelector('.videocontainer__toolbar')
|
||||||
|
.appendChild(nameSpan);
|
||||||
|
|
||||||
|
|
||||||
if (displayName && displayName.length > 0) {
|
if (displayName && displayName.length > 0) {
|
||||||
|
@ -97,12 +100,11 @@ LocalVideo.prototype.setDisplayName = function(displayName, key) {
|
||||||
|
|
||||||
|
|
||||||
nameSpan.id = 'localDisplayName';
|
nameSpan.id = 'localDisplayName';
|
||||||
this.container.appendChild(editButton);
|
|
||||||
//translates popover of edit button
|
//translates popover of edit button
|
||||||
APP.translation.translateElement($("a.displayname"));
|
APP.translation.translateElement($("a.displayname"));
|
||||||
|
|
||||||
var editableText = document.createElement('input');
|
var editableText = document.createElement('input');
|
||||||
editableText.className = 'displayname';
|
editableText.className = 'editdisplayname';
|
||||||
editableText.type = 'text';
|
editableText.type = 'text';
|
||||||
editableText.id = 'editDisplayName';
|
editableText.id = 'editDisplayName';
|
||||||
|
|
||||||
|
@ -119,26 +121,30 @@ LocalVideo.prototype.setDisplayName = function(displayName, key) {
|
||||||
JSON.stringify({name: "Jane Pink"}));
|
JSON.stringify({name: "Jane Pink"}));
|
||||||
editableText.setAttribute("placeholder", defaultNickname);
|
editableText.setAttribute("placeholder", defaultNickname);
|
||||||
|
|
||||||
this.container.appendChild(editableText);
|
this.container
|
||||||
|
.querySelector('.videocontainer__toolbar')
|
||||||
|
.appendChild(editableText);
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
$('#localVideoContainer .displayname')
|
$('#localVideoContainer .displayname')
|
||||||
.bind("click", function (e) {
|
.bind("click", function (e) {
|
||||||
|
let $editDisplayName = $('#editDisplayName');
|
||||||
|
let $localDisplayName = $('#localDisplayName');
|
||||||
|
|
||||||
var editDisplayName = $('#editDisplayName');
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
$('#localDisplayName').hide();
|
$localDisplayName.hide();
|
||||||
editDisplayName.show();
|
$editDisplayName.show();
|
||||||
editDisplayName.focus();
|
$editDisplayName.focus();
|
||||||
editDisplayName.select();
|
$editDisplayName.select();
|
||||||
|
|
||||||
editDisplayName.one("focusout", function (e) {
|
$editDisplayName.one("focusout", function (e) {
|
||||||
self.emitter.emit(UIEvents.NICKNAME_CHANGED, this.value);
|
self.emitter.emit(UIEvents.NICKNAME_CHANGED, this.value);
|
||||||
$('#editDisplayName').hide();
|
$editDisplayName.hide();
|
||||||
|
$localDisplayName.show();
|
||||||
});
|
});
|
||||||
|
|
||||||
editDisplayName.on('keydown', function (e) {
|
$editDisplayName.on('keydown', function (e) {
|
||||||
if (e.keyCode === 13) {
|
if (e.keyCode === 13) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
$('#editDisplayName').hide();
|
$('#editDisplayName').hide();
|
||||||
|
@ -199,7 +205,7 @@ LocalVideo.prototype.changeVideo = function (stream) {
|
||||||
localVideoContainer.removeChild(localVideo);
|
localVideoContainer.removeChild(localVideo);
|
||||||
// when removing only the video element and we are on stage
|
// when removing only the video element and we are on stage
|
||||||
// update the stage
|
// update the stage
|
||||||
if(this.VideoLayout.isCurrentlyOnLarge(this.id))
|
if(this.isCurrentlyOnLargeVideo())
|
||||||
this.VideoLayout.updateLargeVideo(this.id);
|
this.VideoLayout.updateLargeVideo(this.id);
|
||||||
stream.off(TrackEvents.LOCAL_TRACK_STOPPED, endedHandler);
|
stream.off(TrackEvents.LOCAL_TRACK_STOPPED, endedHandler);
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,19 +8,45 @@ import UIUtils from "../util/UIUtil";
|
||||||
import UIEvents from '../../../service/UI/UIEvents';
|
import UIEvents from '../../../service/UI/UIEvents';
|
||||||
import JitsiPopover from "../util/JitsiPopover";
|
import JitsiPopover from "../util/JitsiPopover";
|
||||||
|
|
||||||
function RemoteVideo(id, VideoLayout, emitter) {
|
/**
|
||||||
this.id = id;
|
* Creates new instance of the <tt>RemoteVideo</tt>.
|
||||||
|
* @param user {JitsiParticipant} the user for whom remote video instance will
|
||||||
|
* be created.
|
||||||
|
* @param {VideoLayout} VideoLayout the video layout instance.
|
||||||
|
* @param {EventEmitter} emitter the event emitter which will be used by
|
||||||
|
* the new instance to emit events.
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function RemoteVideo(user, VideoLayout, emitter) {
|
||||||
|
this.user = user;
|
||||||
|
this.id = user.getId();
|
||||||
this.emitter = emitter;
|
this.emitter = emitter;
|
||||||
this.videoSpanId = `participant_${id}`;
|
this.videoSpanId = `participant_${this.id}`;
|
||||||
SmallVideo.call(this, VideoLayout);
|
SmallVideo.call(this, VideoLayout);
|
||||||
this.hasRemoteVideoMenu = false;
|
this.hasRemoteVideoMenu = false;
|
||||||
this.addRemoteVideoContainer();
|
this.addRemoteVideoContainer();
|
||||||
this.connectionIndicator = new ConnectionIndicator(this, id);
|
this.connectionIndicator = new ConnectionIndicator(this, this.id);
|
||||||
this.setDisplayName();
|
this.setDisplayName();
|
||||||
this.bindHoverHandler();
|
|
||||||
this.flipX = false;
|
this.flipX = false;
|
||||||
this.isLocal = false;
|
this.isLocal = false;
|
||||||
this.isMuted = false;
|
/**
|
||||||
|
* The flag is set to <tt>true</tt> after the 'onplay' event has been
|
||||||
|
* triggered on the current video element. It goes back to <tt>false</tt>
|
||||||
|
* when the stream is removed. It is used to determine whether the video
|
||||||
|
* playback has ever started.
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
this.wasVideoPlayed = false;
|
||||||
|
/**
|
||||||
|
* The flag is set to <tt>true</tt> if remote participant's video gets muted
|
||||||
|
* during his media connection disruption. This is to prevent black video
|
||||||
|
* being render on the thumbnail, because even though once the video has
|
||||||
|
* been played the image usually remains on the video element it seems that
|
||||||
|
* after longer period of the video element being hidden this image can be
|
||||||
|
* lost.
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
this.mutedWhileDisconnected = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
RemoteVideo.prototype = Object.create(SmallVideo.prototype);
|
RemoteVideo.prototype = Object.create(SmallVideo.prototype);
|
||||||
|
@ -34,13 +60,15 @@ RemoteVideo.prototype.addRemoteVideoContainer = function() {
|
||||||
if (APP.conference.isModerator) {
|
if (APP.conference.isModerator) {
|
||||||
this.addRemoteVideoMenu();
|
this.addRemoteVideoMenu();
|
||||||
}
|
}
|
||||||
let {thumbWidth, thumbHeight} = this.VideoLayout.resizeThumbnails();
|
|
||||||
AudioLevels.updateAudioLevelCanvas(this.id, thumbWidth, thumbHeight);
|
let { remoteVideo } = this.VideoLayout.resizeThumbnails(false, true);
|
||||||
|
let { thumbHeight, thumbWidth } = remoteVideo;
|
||||||
|
|
||||||
|
this.addAudioLevelIndicator();
|
||||||
|
|
||||||
return this.container;
|
return this.container;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes the remote participant popup menu, by specifying previously
|
* Initializes the remote participant popup menu, by specifying previously
|
||||||
* constructed popupMenuElement, containing all the menu items.
|
* constructed popupMenuElement, containing all the menu items.
|
||||||
|
@ -50,7 +78,7 @@ RemoteVideo.prototype.addRemoteVideoContainer = function() {
|
||||||
*/
|
*/
|
||||||
RemoteVideo.prototype._initPopupMenu = function (popupMenuElement) {
|
RemoteVideo.prototype._initPopupMenu = function (popupMenuElement) {
|
||||||
this.popover = new JitsiPopover(
|
this.popover = new JitsiPopover(
|
||||||
$("#" + this.videoSpanId + " > .remotevideomenu"),
|
$("#" + this.videoSpanId + " .remotevideomenu"),
|
||||||
{ content: popupMenuElement.outerHTML,
|
{ content: popupMenuElement.outerHTML,
|
||||||
skin: "black"});
|
skin: "black"});
|
||||||
|
|
||||||
|
@ -60,7 +88,7 @@ RemoteVideo.prototype._initPopupMenu = function (popupMenuElement) {
|
||||||
this.popover.show = function () {
|
this.popover.show = function () {
|
||||||
// update content by forcing it, to finish even if popover
|
// update content by forcing it, to finish even if popover
|
||||||
// is not visible
|
// is not visible
|
||||||
this.updateRemoteVideoMenu(this.isMuted, true);
|
this.updateRemoteVideoMenu(this.isAudioMuted, true);
|
||||||
// call the original show, passing its actual this
|
// call the original show, passing its actual this
|
||||||
origShowFunc.call(this.popover);
|
origShowFunc.call(this.popover);
|
||||||
}.bind(this);
|
}.bind(this);
|
||||||
|
@ -96,7 +124,7 @@ RemoteVideo.prototype._generatePopupContent = function () {
|
||||||
|
|
||||||
muteLinkItem.id = "mutelink_" + this.id;
|
muteLinkItem.id = "mutelink_" + this.id;
|
||||||
|
|
||||||
if (this.isMuted) {
|
if (this.isAudioMuted) {
|
||||||
muteLinkItem.innerHTML = mutedHTML;
|
muteLinkItem.innerHTML = mutedHTML;
|
||||||
muteLinkItem.className = 'mutelink disabled';
|
muteLinkItem.className = 'mutelink disabled';
|
||||||
}
|
}
|
||||||
|
@ -108,7 +136,7 @@ RemoteVideo.prototype._generatePopupContent = function () {
|
||||||
// Delegate event to the document.
|
// Delegate event to the document.
|
||||||
$(document).on("click", "#mutelink_" + this.id, function(){
|
$(document).on("click", "#mutelink_" + this.id, function(){
|
||||||
|
|
||||||
if (this.isMuted)
|
if (this.isAudioMuted)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this.emitter.emit(UIEvents.REMOTE_AUDIO_MUTED, this.id);
|
this.emitter.emit(UIEvents.REMOTE_AUDIO_MUTED, this.id);
|
||||||
|
@ -152,7 +180,7 @@ RemoteVideo.prototype._generatePopupContent = function () {
|
||||||
*/
|
*/
|
||||||
RemoteVideo.prototype.updateRemoteVideoMenu = function (isMuted, force) {
|
RemoteVideo.prototype.updateRemoteVideoMenu = function (isMuted, force) {
|
||||||
|
|
||||||
this.isMuted = isMuted;
|
this.isAudioMuted = isMuted;
|
||||||
|
|
||||||
// generate content, translate it and add it to document only if
|
// generate content, translate it and add it to document only if
|
||||||
// popover is visible or we force to do so.
|
// popover is visible or we force to do so.
|
||||||
|
@ -161,6 +189,33 @@ RemoteVideo.prototype.updateRemoteVideoMenu = function (isMuted, force) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
RemoteVideo.prototype.setMutedView = function(isMuted) {
|
||||||
|
SmallVideo.prototype.setMutedView.call(this, isMuted);
|
||||||
|
// Update 'mutedWhileDisconnected' flag
|
||||||
|
this._figureOutMutedWhileDisconnected(this.isConnectionActive() === false);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Figures out the value of {@link #mutedWhileDisconnected} flag by taking into
|
||||||
|
* account remote participant's network connectivity and video muted status.
|
||||||
|
*
|
||||||
|
* @param {boolean} isDisconnected <tt>true</tt> if the remote participant is
|
||||||
|
* currently having connectivity issues or <tt>false</tt> otherwise.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
RemoteVideo.prototype._figureOutMutedWhileDisconnected
|
||||||
|
= function(isDisconnected) {
|
||||||
|
if (isDisconnected && this.isVideoMuted) {
|
||||||
|
this.mutedWhileDisconnected = true;
|
||||||
|
} else if (!isDisconnected && !this.isVideoMuted) {
|
||||||
|
this.mutedWhileDisconnected = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds the remote video menu element for the given <tt>id</tt> in the
|
* Adds the remote video menu element for the given <tt>id</tt> in the
|
||||||
* given <tt>parentElement</tt>.
|
* given <tt>parentElement</tt>.
|
||||||
|
@ -170,12 +225,16 @@ RemoteVideo.prototype.updateRemoteVideoMenu = function (isMuted, force) {
|
||||||
*/
|
*/
|
||||||
if (!interfaceConfig.filmStripOnly) {
|
if (!interfaceConfig.filmStripOnly) {
|
||||||
RemoteVideo.prototype.addRemoteVideoMenu = function () {
|
RemoteVideo.prototype.addRemoteVideoMenu = function () {
|
||||||
var spanElement = document.createElement('div');
|
|
||||||
spanElement.className = 'remotevideomenu';
|
var spanElement = document.createElement('span');
|
||||||
this.container.appendChild(spanElement);
|
spanElement.className = 'remotevideomenu toolbar-icon right';
|
||||||
|
|
||||||
|
this.container
|
||||||
|
.querySelector('.videocontainer__toolbar')
|
||||||
|
.appendChild(spanElement);
|
||||||
|
|
||||||
var menuElement = document.createElement('i');
|
var menuElement = document.createElement('i');
|
||||||
menuElement.className = 'fa fa-angle-down';
|
menuElement.className = 'icon-menu-up';
|
||||||
menuElement.title = 'Remote user controls';
|
menuElement.title = 'Remote user controls';
|
||||||
spanElement.appendChild(menuElement);
|
spanElement.appendChild(menuElement);
|
||||||
|
|
||||||
|
@ -204,13 +263,88 @@ RemoteVideo.prototype.removeRemoteStreamElement = function (stream) {
|
||||||
var select = $('#' + elementID);
|
var select = $('#' + elementID);
|
||||||
select.remove();
|
select.remove();
|
||||||
|
|
||||||
|
if (isVideo) {
|
||||||
|
this.wasVideoPlayed = false;
|
||||||
|
}
|
||||||
|
|
||||||
console.info((isVideo ? "Video" : "Audio") +
|
console.info((isVideo ? "Video" : "Audio") +
|
||||||
" removed " + this.id, select);
|
" removed " + this.id, select);
|
||||||
|
|
||||||
// when removing only the video element and we are on stage
|
// when removing only the video element and we are on stage
|
||||||
// update the stage
|
// update the stage
|
||||||
if (isVideo && this.VideoLayout.isCurrentlyOnLarge(this.id))
|
if (isVideo && this.isCurrentlyOnLargeVideo())
|
||||||
this.VideoLayout.updateLargeVideo(this.id);
|
this.VideoLayout.updateLargeVideo(this.id);
|
||||||
|
else
|
||||||
|
// Missing video stream will affect display mode
|
||||||
|
this.updateView();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether the remote user associated with this <tt>RemoteVideo</tt>
|
||||||
|
* has connectivity issues.
|
||||||
|
*
|
||||||
|
* @return {boolean} <tt>true</tt> if the user's connection is fine or
|
||||||
|
* <tt>false</tt> otherwise.
|
||||||
|
*/
|
||||||
|
RemoteVideo.prototype.isConnectionActive = function() {
|
||||||
|
return this.user.isConnectionActive();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The remote video is considered "playable" once the stream has started
|
||||||
|
* according to the {@link #hasVideoStarted} result.
|
||||||
|
*
|
||||||
|
* @inheritdoc
|
||||||
|
* @override
|
||||||
|
*/
|
||||||
|
RemoteVideo.prototype.isVideoPlayable = function () {
|
||||||
|
return SmallVideo.prototype.isVideoPlayable.call(this)
|
||||||
|
&& this.hasVideoStarted() && !this.mutedWhileDisconnected;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
RemoteVideo.prototype.updateView = function () {
|
||||||
|
|
||||||
|
this.updateConnectionStatusIndicator(
|
||||||
|
null /* will obtain the status from 'conference' */);
|
||||||
|
|
||||||
|
// This must be called after 'updateConnectionStatusIndicator' because it
|
||||||
|
// affects the display mode by modifying 'mutedWhileDisconnected' flag
|
||||||
|
SmallVideo.prototype.updateView.call(this);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the UI to reflect user's connectivity status.
|
||||||
|
* @param isActive {boolean|null} 'true' if user's connection is active or
|
||||||
|
* 'false' when the use is having some connectivity issues and a warning
|
||||||
|
* should be displayed. When 'null' is passed then the current value will be
|
||||||
|
* obtained from the conference instance.
|
||||||
|
*/
|
||||||
|
RemoteVideo.prototype.updateConnectionStatusIndicator = function (isActive) {
|
||||||
|
// Check for initial value if 'isActive' is not defined
|
||||||
|
if (typeof isActive !== "boolean") {
|
||||||
|
isActive = this.isConnectionActive();
|
||||||
|
if (isActive === null) {
|
||||||
|
// Cancel processing at this point - no update
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.debug(this.id + " thumbnail is connection active ? " + isActive);
|
||||||
|
|
||||||
|
// Update 'mutedWhileDisconnected' flag
|
||||||
|
this._figureOutMutedWhileDisconnected(!isActive);
|
||||||
|
|
||||||
|
if(this.connectionIndicator)
|
||||||
|
this.connectionIndicator.updateConnectionStatusIndicator(isActive);
|
||||||
|
|
||||||
|
// Toggle thumbnail video problem filter
|
||||||
|
this.selectVideoElement().toggleClass(
|
||||||
|
"videoThumbnailProblemFilter", !isActive);
|
||||||
|
this.$avatar().toggleClass(
|
||||||
|
"videoThumbnailProblemFilter", !isActive);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -241,22 +375,23 @@ RemoteVideo.prototype.waitForPlayback = function (streamElement, stream) {
|
||||||
// Register 'onplaying' listener to trigger 'videoactive' on VideoLayout
|
// Register 'onplaying' listener to trigger 'videoactive' on VideoLayout
|
||||||
// when video playback starts
|
// when video playback starts
|
||||||
var onPlayingHandler = function () {
|
var onPlayingHandler = function () {
|
||||||
|
self.wasVideoPlayed = true;
|
||||||
self.VideoLayout.videoactive(streamElement, self.id);
|
self.VideoLayout.videoactive(streamElement, self.id);
|
||||||
streamElement.onplaying = null;
|
streamElement.onplaying = null;
|
||||||
|
// Refresh to show the video
|
||||||
|
self.updateView();
|
||||||
};
|
};
|
||||||
streamElement.onplaying = onPlayingHandler;
|
streamElement.onplaying = onPlayingHandler;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether or not video stream exists and has started for this
|
* Checks whether the video stream has started for this RemoteVideo instance.
|
||||||
* RemoteVideo instance. This is checked by trying to select video element in
|
|
||||||
* this container and checking if 'currentTime' field's value is greater than 0.
|
|
||||||
*
|
*
|
||||||
* @returns {*|boolean} true if this RemoteVideo has active video stream running
|
* @returns {boolean} true if this RemoteVideo has a video stream for which
|
||||||
|
* the playback has been started.
|
||||||
*/
|
*/
|
||||||
RemoteVideo.prototype.hasVideoStarted = function () {
|
RemoteVideo.prototype.hasVideoStarted = function () {
|
||||||
var videoSelector = this.selectVideoElement();
|
return this.wasVideoPlayed;
|
||||||
return videoSelector.length && videoSelector[0].currentTime > 0;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
RemoteVideo.prototype.addRemoteStreamElement = function (stream) {
|
RemoteVideo.prototype.addRemoteStreamElement = function (stream) {
|
||||||
|
@ -381,7 +516,7 @@ RemoteVideo.prototype.setDisplayName = function(displayName, key) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var nameSpan = $('#' + this.videoSpanId + '>span.displayname');
|
var nameSpan = $('#' + this.videoSpanId + ' .displayname');
|
||||||
|
|
||||||
// If we already have a display name for this video.
|
// If we already have a display name for this video.
|
||||||
if (nameSpan.length > 0) {
|
if (nameSpan.length > 0) {
|
||||||
|
@ -400,7 +535,9 @@ RemoteVideo.prototype.setDisplayName = function(displayName, key) {
|
||||||
} else {
|
} else {
|
||||||
nameSpan = document.createElement('span');
|
nameSpan = document.createElement('span');
|
||||||
nameSpan.className = 'displayname';
|
nameSpan.className = 'displayname';
|
||||||
$('#' + this.videoSpanId)[0].appendChild(nameSpan);
|
$('#' + this.videoSpanId)[0]
|
||||||
|
.querySelector('.videocontainer__toolbar')
|
||||||
|
.appendChild(nameSpan);
|
||||||
|
|
||||||
if (displayName && displayName.length > 0) {
|
if (displayName && displayName.length > 0) {
|
||||||
$(nameSpan).text(displayName);
|
$(nameSpan).text(displayName);
|
||||||
|
@ -418,7 +555,7 @@ RemoteVideo.prototype.setDisplayName = function(displayName, key) {
|
||||||
* @param videoElementId the id of local or remote video element.
|
* @param videoElementId the id of local or remote video element.
|
||||||
*/
|
*/
|
||||||
RemoteVideo.prototype.removeRemoteVideoMenu = function() {
|
RemoteVideo.prototype.removeRemoteVideoMenu = function() {
|
||||||
var menuSpan = $('#' + this.videoSpanId + '>span.remotevideomenu');
|
var menuSpan = $('#' + this.videoSpanId + '> .remotevideomenu');
|
||||||
if (menuSpan.length) {
|
if (menuSpan.length) {
|
||||||
this.popover.forceHide();
|
this.popover.forceHide();
|
||||||
menuSpan.remove();
|
menuSpan.remove();
|
||||||
|
@ -427,12 +564,16 @@ RemoteVideo.prototype.removeRemoteVideoMenu = function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
RemoteVideo.createContainer = function (spanId) {
|
RemoteVideo.createContainer = function (spanId) {
|
||||||
var container = document.createElement('span');
|
let container = document.createElement('span');
|
||||||
container.id = spanId;
|
container.id = spanId;
|
||||||
container.className = 'videocontainer';
|
container.className = 'videocontainer';
|
||||||
|
|
||||||
|
let toolbar = document.createElement('div');
|
||||||
|
toolbar.className = "videocontainer__toolbar";
|
||||||
|
container.appendChild(toolbar);
|
||||||
|
|
||||||
var remotes = document.getElementById('remoteVideos');
|
var remotes = document.getElementById('remoteVideos');
|
||||||
return remotes.appendChild(container);
|
return remotes.appendChild(container);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export default RemoteVideo;
|
export default RemoteVideo;
|
||||||
|
|
|
@ -1,13 +1,34 @@
|
||||||
/* global $, APP, JitsiMeetJS */
|
/* global $, APP, JitsiMeetJS, interfaceConfig */
|
||||||
/* jshint -W101 */
|
|
||||||
import Avatar from "../avatar/Avatar";
|
import Avatar from "../avatar/Avatar";
|
||||||
import UIUtil from "../util/UIUtil";
|
import UIUtil from "../util/UIUtil";
|
||||||
import UIEvents from "../../../service/UI/UIEvents";
|
import UIEvents from "../../../service/UI/UIEvents";
|
||||||
|
import AudioLevels from "../audio_levels/AudioLevels";
|
||||||
|
|
||||||
const RTCUIHelper = JitsiMeetJS.util.RTCUIHelper;
|
const RTCUIHelper = JitsiMeetJS.util.RTCUIHelper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display mode constant used when video is being displayed on the small video.
|
||||||
|
* @type {number}
|
||||||
|
* @constant
|
||||||
|
*/
|
||||||
|
const DISPLAY_VIDEO = 0;
|
||||||
|
/**
|
||||||
|
* Display mode constant used when the user's avatar is being displayed on
|
||||||
|
* the small video.
|
||||||
|
* @type {number}
|
||||||
|
* @constant
|
||||||
|
*/
|
||||||
|
const DISPLAY_AVATAR = 1;
|
||||||
|
/**
|
||||||
|
* Display mode constant used when neither video nor avatar is being displayed
|
||||||
|
* on the small video.
|
||||||
|
* @type {number}
|
||||||
|
* @constant
|
||||||
|
*/
|
||||||
|
const DISPLAY_BLACKNESS = 2;
|
||||||
|
|
||||||
function SmallVideo(VideoLayout) {
|
function SmallVideo(VideoLayout) {
|
||||||
this.isMuted = false;
|
this.isAudioMuted = false;
|
||||||
this.hasAvatar = false;
|
this.hasAvatar = false;
|
||||||
this.isVideoMuted = false;
|
this.isVideoMuted = false;
|
||||||
this.videoStream = null;
|
this.videoStream = null;
|
||||||
|
@ -40,7 +61,7 @@ SmallVideo.prototype.isVisible = function () {
|
||||||
};
|
};
|
||||||
|
|
||||||
SmallVideo.prototype.showDisplayName = function(isShow) {
|
SmallVideo.prototype.showDisplayName = function(isShow) {
|
||||||
var nameSpan = $('#' + this.videoSpanId + '>span.displayname').get(0);
|
var nameSpan = $('#' + this.videoSpanId + ' .displayname').get(0);
|
||||||
if (isShow) {
|
if (isShow) {
|
||||||
if (nameSpan && nameSpan.innerHTML && nameSpan.innerHTML.length)
|
if (nameSpan && nameSpan.innerHTML && nameSpan.innerHTML.length)
|
||||||
nameSpan.setAttribute("style", "display:inline-block;");
|
nameSpan.setAttribute("style", "display:inline-block;");
|
||||||
|
@ -171,26 +192,6 @@ SmallVideo.getStreamElementID = function (stream) {
|
||||||
return (isVideo ? 'remoteVideo_' : 'remoteAudio_') + stream.getId();
|
return (isVideo ? 'remoteVideo_' : 'remoteAudio_') + stream.getId();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Configures hoverIn/hoverOut handlers.
|
|
||||||
*/
|
|
||||||
SmallVideo.prototype.bindHoverHandler = function () {
|
|
||||||
// Add hover handler
|
|
||||||
var self = this;
|
|
||||||
$(this.container).hover(
|
|
||||||
function () {
|
|
||||||
self.showDisplayName(true);
|
|
||||||
},
|
|
||||||
function () {
|
|
||||||
// If the video has been "pinned" by the user we want to
|
|
||||||
// keep the display name on place.
|
|
||||||
if (!self.VideoLayout.isLargeVideoVisible() ||
|
|
||||||
!self.VideoLayout.isCurrentlyOnLarge(self.id))
|
|
||||||
self.showDisplayName(false);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the data for the indicator
|
* Updates the data for the indicator
|
||||||
* @param id the id of the indicator
|
* @param id the id of the indicator
|
||||||
|
@ -209,121 +210,164 @@ SmallVideo.prototype.hideIndicator = function () {
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows audio muted indicator over small videos.
|
* Shows / hides the audio muted indicator over small videos.
|
||||||
* @param {string} isMuted
|
*
|
||||||
|
* @param {boolean} isMuted indicates if the muted element should be shown
|
||||||
|
* or hidden
|
||||||
*/
|
*/
|
||||||
SmallVideo.prototype.showAudioIndicator = function(isMuted) {
|
SmallVideo.prototype.showAudioIndicator = function(isMuted) {
|
||||||
var audioMutedSpan = $('#' + this.videoSpanId + '>span.audioMuted');
|
|
||||||
|
var audioMutedIndicator = this.getAudioMutedIndicator();
|
||||||
|
|
||||||
if (!isMuted) {
|
if (!isMuted) {
|
||||||
if (audioMutedSpan.length > 0) {
|
audioMutedIndicator.hide();
|
||||||
audioMutedSpan.popover('hide');
|
|
||||||
audioMutedSpan.remove();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (!audioMutedSpan.length) {
|
audioMutedIndicator.show();
|
||||||
audioMutedSpan = document.createElement('span');
|
|
||||||
audioMutedSpan.className = 'audioMuted';
|
|
||||||
UIUtil.setTooltip(audioMutedSpan,
|
|
||||||
"videothumbnail.mute",
|
|
||||||
"top");
|
|
||||||
|
|
||||||
this.container.appendChild(audioMutedSpan);
|
|
||||||
APP.translation.translateElement($('#' + this.videoSpanId + " > span"));
|
|
||||||
var mutedIndicator = document.createElement('i');
|
|
||||||
mutedIndicator.className = 'icon-mic-disabled';
|
|
||||||
audioMutedSpan.appendChild(mutedIndicator);
|
|
||||||
|
|
||||||
}
|
|
||||||
this.updateIconPositions();
|
|
||||||
}
|
}
|
||||||
this.isMuted = isMuted;
|
this.isAudioMuted = isMuted;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the audio muted indicator jquery object. If it doesn't exists -
|
||||||
|
* creates it.
|
||||||
|
*
|
||||||
|
* @returns {jQuery|HTMLElement} the audio muted indicator
|
||||||
|
*/
|
||||||
|
SmallVideo.prototype.getAudioMutedIndicator = function () {
|
||||||
|
var audioMutedSpan = $('#' + this.videoSpanId + ' .audioMuted');
|
||||||
|
|
||||||
|
if (audioMutedSpan.length) {
|
||||||
|
return audioMutedSpan;
|
||||||
|
}
|
||||||
|
|
||||||
|
audioMutedSpan = document.createElement('span');
|
||||||
|
audioMutedSpan.className = 'audioMuted toolbar-icon';
|
||||||
|
|
||||||
|
UIUtil.setTooltip(audioMutedSpan,
|
||||||
|
"videothumbnail.mute",
|
||||||
|
"top");
|
||||||
|
|
||||||
|
this.container
|
||||||
|
.querySelector('.videocontainer__toolbar')
|
||||||
|
.appendChild(audioMutedSpan);
|
||||||
|
|
||||||
|
|
||||||
|
var mutedIndicator = document.createElement('i');
|
||||||
|
mutedIndicator.className = 'icon-mic-disabled';
|
||||||
|
audioMutedSpan.appendChild(mutedIndicator);
|
||||||
|
|
||||||
|
return $('#' + this.videoSpanId + ' .audioMuted');
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows video muted indicator over small videos and disables/enables avatar
|
* Shows video muted indicator over small videos and disables/enables avatar
|
||||||
* if video muted.
|
* if video muted.
|
||||||
|
*
|
||||||
|
* @param {boolean} isMuted indicates if we should set the view to muted view
|
||||||
|
* or not
|
||||||
*/
|
*/
|
||||||
SmallVideo.prototype.setMutedView = function(isMuted) {
|
SmallVideo.prototype.setVideoMutedView = function(isMuted) {
|
||||||
this.isVideoMuted = isMuted;
|
this.isVideoMuted = isMuted;
|
||||||
this.updateView();
|
this.updateView();
|
||||||
|
|
||||||
var videoMutedSpan = $('#' + this.videoSpanId + '>span.videoMuted');
|
var videoMutedSpan = this.getVideoMutedIndicator();
|
||||||
|
|
||||||
if (isMuted === false) {
|
videoMutedSpan[isMuted ? 'show' : 'hide']();
|
||||||
if (videoMutedSpan.length > 0) {
|
|
||||||
videoMutedSpan.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (!videoMutedSpan.length) {
|
|
||||||
videoMutedSpan = document.createElement('span');
|
|
||||||
videoMutedSpan.className = 'videoMuted';
|
|
||||||
|
|
||||||
this.container.appendChild(videoMutedSpan);
|
|
||||||
|
|
||||||
var mutedIndicator = document.createElement('i');
|
|
||||||
mutedIndicator.className = 'icon-camera-disabled';
|
|
||||||
UIUtil.setTooltip(mutedIndicator,
|
|
||||||
"videothumbnail.videomute",
|
|
||||||
"top");
|
|
||||||
videoMutedSpan.appendChild(mutedIndicator);
|
|
||||||
//translate texts for muted indicator
|
|
||||||
APP.translation.translateElement($('#' + this.videoSpanId + " > span > i"));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.updateIconPositions();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
SmallVideo.prototype.updateIconPositions = function () {
|
|
||||||
var audioMutedSpan = $('#' + this.videoSpanId + '>span.audioMuted');
|
|
||||||
var connectionIndicator = $('#' + this.videoSpanId + '>div.connectionindicator');
|
|
||||||
var videoMutedSpan = $('#' + this.videoSpanId + '>span.videoMuted');
|
|
||||||
if(connectionIndicator.length > 0 &&
|
|
||||||
connectionIndicator[0].style.display != "none") {
|
|
||||||
audioMutedSpan.css({right: "23px"});
|
|
||||||
videoMutedSpan.css({right: ((audioMutedSpan.length > 0? 23 : 0) + 30) + "px"});
|
|
||||||
} else {
|
|
||||||
audioMutedSpan.css({right: "0px"});
|
|
||||||
videoMutedSpan.css({right: (audioMutedSpan.length > 0? 30 : 0) + "px"});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates the element indicating the moderator(owner) of the conference.
|
* Returns the video muted indicator jquery object. If it doesn't exists -
|
||||||
|
* creates it.
|
||||||
|
*
|
||||||
|
* @returns {jQuery|HTMLElement} the video muted indicator
|
||||||
*/
|
*/
|
||||||
SmallVideo.prototype.createModeratorIndicatorElement = function () {
|
SmallVideo.prototype.getVideoMutedIndicator = function () {
|
||||||
|
var videoMutedSpan = $('#' + this.videoSpanId + ' .videoMuted');
|
||||||
|
|
||||||
|
if (videoMutedSpan.length) {
|
||||||
|
return videoMutedSpan;
|
||||||
|
}
|
||||||
|
|
||||||
|
videoMutedSpan = document.createElement('span');
|
||||||
|
videoMutedSpan.className = 'videoMuted toolbar-icon';
|
||||||
|
|
||||||
|
this.container
|
||||||
|
.querySelector('.videocontainer__toolbar')
|
||||||
|
.appendChild(videoMutedSpan);
|
||||||
|
|
||||||
|
var mutedIndicator = document.createElement('i');
|
||||||
|
mutedIndicator.className = 'icon-camera-disabled';
|
||||||
|
|
||||||
|
UIUtil.setTooltip(mutedIndicator,
|
||||||
|
"videothumbnail.videomute",
|
||||||
|
"top");
|
||||||
|
|
||||||
|
videoMutedSpan.appendChild(mutedIndicator);
|
||||||
|
|
||||||
|
return $('#' + this.videoSpanId + ' .videoMuted');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the element indicating the moderator(owner) of the conference.
|
||||||
|
*/
|
||||||
|
SmallVideo.prototype.addModeratorIndicator = function () {
|
||||||
|
|
||||||
|
// Don't create moderator indicator if DISABLE_FOCUS_INDICATOR is true
|
||||||
|
if (interfaceConfig.DISABLE_FOCUS_INDICATOR)
|
||||||
|
return false;
|
||||||
|
|
||||||
// Show moderator indicator
|
// Show moderator indicator
|
||||||
var indicatorSpan = $('#' + this.videoSpanId + ' .focusindicator');
|
var indicatorSpan = $('#' + this.videoSpanId + ' .focusindicator');
|
||||||
|
|
||||||
if (!indicatorSpan || indicatorSpan.length === 0) {
|
if (indicatorSpan.length) {
|
||||||
indicatorSpan = document.createElement('span');
|
return;
|
||||||
indicatorSpan.className = 'focusindicator';
|
|
||||||
|
|
||||||
this.container.appendChild(indicatorSpan);
|
|
||||||
indicatorSpan = $('#' + this.videoSpanId + ' .focusindicator');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (indicatorSpan.children().length !== 0)
|
indicatorSpan = document.createElement('span');
|
||||||
return;
|
indicatorSpan.className = 'focusindicator toolbar-icon right';
|
||||||
|
|
||||||
|
this.container
|
||||||
|
.querySelector('.videocontainer__toolbar')
|
||||||
|
.appendChild(indicatorSpan);
|
||||||
|
|
||||||
var moderatorIndicator = document.createElement('i');
|
var moderatorIndicator = document.createElement('i');
|
||||||
moderatorIndicator.className = 'icon-star';
|
moderatorIndicator.className = 'icon-star';
|
||||||
indicatorSpan[0].appendChild(moderatorIndicator);
|
|
||||||
|
|
||||||
UIUtil.setTooltip(indicatorSpan[0],
|
UIUtil.setTooltip(moderatorIndicator,
|
||||||
"videothumbnail.moderator",
|
"videothumbnail.moderator",
|
||||||
"top");
|
"top-left");
|
||||||
|
|
||||||
//translates text in focus indicators
|
indicatorSpan.appendChild(moderatorIndicator);
|
||||||
APP.translation.translateElement($('#' + this.videoSpanId + ' .focusindicator'));
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the element indicating the audio level of the participant.
|
||||||
|
*/
|
||||||
|
SmallVideo.prototype.addAudioLevelIndicator = function () {
|
||||||
|
var audioSpan = $('#' + this.videoSpanId + ' .audioindicator');
|
||||||
|
|
||||||
|
if (audioSpan.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.container.appendChild(
|
||||||
|
AudioLevels.createThumbnailAudioLevelIndicator());
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the audio level for this small video.
|
||||||
|
*
|
||||||
|
* @param lvl the new audio level to set
|
||||||
|
*/
|
||||||
|
SmallVideo.prototype.updateAudioLevelIndicator = function (lvl) {
|
||||||
|
AudioLevels.updateThumbnailAudioLevel(this.videoSpanId, lvl);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes the element indicating the moderator(owner) of the conference.
|
* Removes the element indicating the moderator(owner) of the conference.
|
||||||
*/
|
*/
|
||||||
SmallVideo.prototype.removeModeratorIndicatorElement = function () {
|
SmallVideo.prototype.removeModeratorIndicator = function () {
|
||||||
$('#' + this.videoSpanId + ' .focusindicator').remove();
|
$('#' + this.videoSpanId + ' .focusindicator').remove();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -341,6 +385,16 @@ SmallVideo.prototype.selectVideoElement = function () {
|
||||||
return $(RTCUIHelper.findVideoElement($('#' + this.videoSpanId)[0]));
|
return $(RTCUIHelper.findVideoElement($('#' + this.videoSpanId)[0]));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selects the HTML image element which displays user's avatar.
|
||||||
|
*
|
||||||
|
* @return {jQuery|HTMLElement} a jQuery selector pointing to the HTML image
|
||||||
|
* element which displays the user's avatar.
|
||||||
|
*/
|
||||||
|
SmallVideo.prototype.$avatar = function () {
|
||||||
|
return $('#' + this.videoSpanId + ' .userAvatar');
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enables / disables the css responsible for focusing/pinning a video
|
* Enables / disables the css responsible for focusing/pinning a video
|
||||||
* thumbnail.
|
* thumbnail.
|
||||||
|
@ -363,6 +417,47 @@ SmallVideo.prototype.hasVideo = function () {
|
||||||
return this.selectVideoElement().length !== 0;
|
return this.selectVideoElement().length !== 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether the user associated with this <tt>SmallVideo</tt> is currently
|
||||||
|
* being displayed on the "large video".
|
||||||
|
*
|
||||||
|
* @return {boolean} <tt>true</tt> if the user is displayed on the large video
|
||||||
|
* or <tt>false</tt> otherwise.
|
||||||
|
*/
|
||||||
|
SmallVideo.prototype.isCurrentlyOnLargeVideo = function () {
|
||||||
|
return this.VideoLayout.isCurrentlyOnLarge(this.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether there is a playable video stream available for the user
|
||||||
|
* associated with this <tt>SmallVideo</tt>.
|
||||||
|
*
|
||||||
|
* @return {boolean} <tt>true</tt> if there is a playable video stream available
|
||||||
|
* or <tt>false</tt> otherwise.
|
||||||
|
*/
|
||||||
|
SmallVideo.prototype.isVideoPlayable = function() {
|
||||||
|
return this.videoStream // Is there anything to display ?
|
||||||
|
&& !this.isVideoMuted && !this.videoStream.isMuted() // Muted ?
|
||||||
|
&& (this.isLocal || this.VideoLayout.isInLastN(this.id));
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines what should be display on the thumbnail.
|
||||||
|
*
|
||||||
|
* @return {number} one of <tt>DISPLAY_VIDEO</tt>,<tt>DISPLAY_AVATAR</tt>
|
||||||
|
* or <tt>DISPLAY_BLACKNESS</tt>.
|
||||||
|
*/
|
||||||
|
SmallVideo.prototype.selectDisplayMode = function() {
|
||||||
|
// Display name is always and only displayed when user is on the stage
|
||||||
|
if (this.isCurrentlyOnLargeVideo()) {
|
||||||
|
return DISPLAY_BLACKNESS;
|
||||||
|
} else if (this.isVideoPlayable() && this.selectVideoElement().length) {
|
||||||
|
return DISPLAY_VIDEO;
|
||||||
|
} else {
|
||||||
|
return DISPLAY_AVATAR;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hides or shows the user's avatar.
|
* Hides or shows the user's avatar.
|
||||||
* This update assumes that large video had been updated and we will
|
* This update assumes that large video had been updated and we will
|
||||||
|
@ -382,48 +477,28 @@ SmallVideo.prototype.updateView = function () {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let video = this.selectVideoElement();
|
// Determine whether video, avatar or blackness should be displayed
|
||||||
|
let displayMode = this.selectDisplayMode();
|
||||||
let avatar = $('#' + this.videoSpanId + ' .userAvatar');
|
// Show/hide video
|
||||||
|
setVisibility(this.selectVideoElement(), displayMode === DISPLAY_VIDEO);
|
||||||
var isCurrentlyOnLarge = this.VideoLayout.isCurrentlyOnLarge(this.id);
|
// Show/hide the avatar
|
||||||
|
setVisibility(this.$avatar(), displayMode === DISPLAY_AVATAR);
|
||||||
var showVideo = !this.isVideoMuted && !isCurrentlyOnLarge;
|
|
||||||
var showAvatar;
|
|
||||||
if ((!this.isLocal
|
|
||||||
&& !this.VideoLayout.isInLastN(this.id))
|
|
||||||
|| this.isVideoMuted) {
|
|
||||||
showAvatar = true;
|
|
||||||
} else {
|
|
||||||
// We want to show the avatar when the video is muted or not exists
|
|
||||||
// that is when 'true' or 'null' is returned
|
|
||||||
showAvatar = !this.videoStream || this.videoStream.isMuted();
|
|
||||||
}
|
|
||||||
|
|
||||||
showAvatar = showAvatar && !isCurrentlyOnLarge;
|
|
||||||
|
|
||||||
if (video && video.length > 0) {
|
|
||||||
setVisibility(video, showVideo);
|
|
||||||
}
|
|
||||||
setVisibility(avatar, showAvatar);
|
|
||||||
|
|
||||||
this.showDisplayName(!showVideo && !showAvatar);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
SmallVideo.prototype.avatarChanged = function (avatarUrl) {
|
SmallVideo.prototype.avatarChanged = function (avatarUrl) {
|
||||||
var thumbnail = $('#' + this.videoSpanId);
|
var thumbnail = $('#' + this.videoSpanId);
|
||||||
var avatar = $('#' + this.videoSpanId + ' .userAvatar');
|
var avatarSel = this.$avatar();
|
||||||
this.hasAvatar = true;
|
this.hasAvatar = true;
|
||||||
|
|
||||||
// set the avatar in the thumbnail
|
// set the avatar in the thumbnail
|
||||||
if (avatar && avatar.length > 0) {
|
if (avatarSel && avatarSel.length > 0) {
|
||||||
avatar[0].src = avatarUrl;
|
avatarSel[0].src = avatarUrl;
|
||||||
} else {
|
} else {
|
||||||
if (thumbnail && thumbnail.length > 0) {
|
if (thumbnail && thumbnail.length > 0) {
|
||||||
avatar = document.createElement('img');
|
var avatarElement = document.createElement('img');
|
||||||
avatar.className = 'userAvatar';
|
avatarElement.className = 'userAvatar';
|
||||||
avatar.src = avatarUrl;
|
avatarElement.src = avatarUrl;
|
||||||
thumbnail.append(avatar);
|
thumbnail.append(avatarElement);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -445,7 +520,7 @@ SmallVideo.prototype.showDominantSpeakerIndicator = function (show) {
|
||||||
indicatorSpan.innerHTML
|
indicatorSpan.innerHTML
|
||||||
= "<i id='indicatoricon' class='fa fa-bullhorn'></i>";
|
= "<i id='indicatoricon' class='fa fa-bullhorn'></i>";
|
||||||
// adds a tooltip
|
// adds a tooltip
|
||||||
UIUtil.setTooltip(indicatorSpan, "speaker", "left");
|
UIUtil.setTooltip(indicatorSpan, "speaker", "top");
|
||||||
APP.translation.translateElement($(indicatorSpan));
|
APP.translation.translateElement($(indicatorSpan));
|
||||||
|
|
||||||
$(indicatorSpan).css("visibility", show ? "visible" : "hidden");
|
$(indicatorSpan).css("visibility", show ? "visible" : "hidden");
|
||||||
|
@ -465,12 +540,11 @@ SmallVideo.prototype.showRaisedHandIndicator = function (show) {
|
||||||
var indicatorSpanId = "raisehandindicator";
|
var indicatorSpanId = "raisehandindicator";
|
||||||
var indicatorSpan = this.getIndicatorSpan(indicatorSpanId);
|
var indicatorSpan = this.getIndicatorSpan(indicatorSpanId);
|
||||||
|
|
||||||
indicatorSpan.style.background = "#D6D61E";
|
|
||||||
indicatorSpan.innerHTML
|
indicatorSpan.innerHTML
|
||||||
= "<i id='indicatoricon' class='fa fa-hand-paper-o'></i>";
|
= "<i id='indicatoricon' class='icon-raised-hand'></i>";
|
||||||
|
|
||||||
// adds a tooltip
|
// adds a tooltip
|
||||||
UIUtil.setTooltip(indicatorSpan, "raisedHand", "left");
|
UIUtil.setTooltip(indicatorSpan, "raisedHand", "top");
|
||||||
APP.translation.translateElement($(indicatorSpan));
|
APP.translation.translateElement($(indicatorSpan));
|
||||||
|
|
||||||
$(indicatorSpan).css("visibility", show ? "visible" : "hidden");
|
$(indicatorSpan).css("visibility", show ? "visible" : "hidden");
|
||||||
|
|
|
@ -1,17 +1,16 @@
|
||||||
/* global $, APP, interfaceConfig */
|
/* global $, APP, interfaceConfig */
|
||||||
/* jshint -W101 */
|
/* jshint -W101 */
|
||||||
|
|
||||||
import UIUtil from "../util/UIUtil";
|
|
||||||
import UIEvents from "../../../service/UI/UIEvents";
|
|
||||||
import LargeContainer from './LargeContainer';
|
|
||||||
import FilmStrip from './FilmStrip';
|
import FilmStrip from './FilmStrip';
|
||||||
import Avatar from "../avatar/Avatar";
|
import LargeContainer from './LargeContainer';
|
||||||
import {createDeferred} from '../../util/helpers';
|
import UIEvents from "../../../service/UI/UIEvents";
|
||||||
|
import UIUtil from "../util/UIUtil";
|
||||||
|
|
||||||
|
// FIXME should be 'video'
|
||||||
|
export const VIDEO_CONTAINER_TYPE = "camera";
|
||||||
|
|
||||||
const FADE_DURATION_MS = 300;
|
const FADE_DURATION_MS = 300;
|
||||||
|
|
||||||
export const VIDEO_CONTAINER_TYPE = "camera";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get stream id.
|
* Get stream id.
|
||||||
* @param {JitsiTrack?} stream
|
* @param {JitsiTrack?} stream
|
||||||
|
@ -20,7 +19,8 @@ function getStreamOwnerId(stream) {
|
||||||
if (!stream) {
|
if (!stream) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (stream.isLocal()) { // local stream doesn't have method "getParticipantId"
|
// local stream doesn't have method "getParticipantId"
|
||||||
|
if (stream.isLocal()) {
|
||||||
return APP.conference.getMyUserId();
|
return APP.conference.getMyUserId();
|
||||||
} else {
|
} else {
|
||||||
return stream.getParticipantId();
|
return stream.getParticipantId();
|
||||||
|
@ -154,7 +154,7 @@ function getDesktopVideoPosition(videoWidth,
|
||||||
/**
|
/**
|
||||||
* Container for user video.
|
* Container for user video.
|
||||||
*/
|
*/
|
||||||
class VideoContainer extends LargeContainer {
|
export class VideoContainer extends LargeContainer {
|
||||||
// FIXME: With Temasys we have to re-select everytime
|
// FIXME: With Temasys we have to re-select everytime
|
||||||
get $video () {
|
get $video () {
|
||||||
return $('#largeVideo');
|
return $('#largeVideo');
|
||||||
|
@ -164,23 +164,61 @@ class VideoContainer extends LargeContainer {
|
||||||
return getStreamOwnerId(this.stream);
|
return getStreamOwnerId(this.stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor (onPlay) {
|
constructor (onPlay, emitter) {
|
||||||
super();
|
super();
|
||||||
this.stream = null;
|
this.stream = null;
|
||||||
this.videoType = null;
|
this.videoType = null;
|
||||||
this.localFlipX = true;
|
this.localFlipX = true;
|
||||||
|
this.emitter = emitter;
|
||||||
|
|
||||||
this.isVisible = false;
|
this.isVisible = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag indicates whether or not the avatar is currently displayed.
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
this.avatarDisplayed = false;
|
||||||
this.$avatar = $('#dominantSpeaker');
|
this.$avatar = $('#dominantSpeaker');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A jQuery selector of the remote connection message.
|
||||||
|
* @type {jQuery|HTMLElement}
|
||||||
|
*/
|
||||||
|
this.$remoteConnectionMessage = $('#remoteConnectionMessage');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether or not the video stream attached to the video
|
||||||
|
* element has started(which means that there is any image rendered
|
||||||
|
* even if the video is stalled).
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
this.wasVideoRendered = false;
|
||||||
|
|
||||||
this.$wrapper = $('#largeVideoWrapper');
|
this.$wrapper = $('#largeVideoWrapper');
|
||||||
|
|
||||||
this.avatarHeight = $("#dominantSpeakerAvatar").height();
|
this.avatarHeight = $("#dominantSpeakerAvatar").height();
|
||||||
|
|
||||||
|
var onPlayCallback = function (event) {
|
||||||
|
if (typeof onPlay === 'function') {
|
||||||
|
onPlay(event);
|
||||||
|
}
|
||||||
|
this.wasVideoRendered = true;
|
||||||
|
}.bind(this);
|
||||||
// This does not work with Temasys plugin - has to be a property to be
|
// This does not work with Temasys plugin - has to be a property to be
|
||||||
// copied between new <object> elements
|
// copied between new <object> elements
|
||||||
//this.$video.on('play', onPlay);
|
//this.$video.on('play', onPlay);
|
||||||
this.$video[0].onplay = onPlay;
|
this.$video[0].onplay = onPlayCallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables a filter on the video which indicates that there are some
|
||||||
|
* problems with the local media connection.
|
||||||
|
*
|
||||||
|
* @param {boolean} enable <tt>true</tt> if the filter is to be enabled or
|
||||||
|
* <tt>false</tt> otherwise.
|
||||||
|
*/
|
||||||
|
enableLocalConnectionProblemFilter (enable) {
|
||||||
|
this.$video.toggleClass("videoProblemFilter", enable);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -205,14 +243,14 @@ class VideoContainer extends LargeContainer {
|
||||||
let { width, height } = this.getStreamSize();
|
let { width, height } = this.getStreamSize();
|
||||||
if (this.stream && this.isScreenSharing()) {
|
if (this.stream && this.isScreenSharing()) {
|
||||||
return getDesktopVideoSize( width,
|
return getDesktopVideoSize( width,
|
||||||
height,
|
height,
|
||||||
containerWidth,
|
containerWidth,
|
||||||
containerHeight);
|
containerHeight);
|
||||||
} else {
|
} else {
|
||||||
return getCameraVideoSize( width,
|
return getCameraVideoSize( width,
|
||||||
height,
|
height,
|
||||||
containerWidth,
|
containerWidth,
|
||||||
containerHeight);
|
containerHeight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -228,29 +266,55 @@ class VideoContainer extends LargeContainer {
|
||||||
getVideoPosition (width, height, containerWidth, containerHeight) {
|
getVideoPosition (width, height, containerWidth, containerHeight) {
|
||||||
if (this.stream && this.isScreenSharing()) {
|
if (this.stream && this.isScreenSharing()) {
|
||||||
return getDesktopVideoPosition( width,
|
return getDesktopVideoPosition( width,
|
||||||
height,
|
height,
|
||||||
containerWidth,
|
containerWidth,
|
||||||
containerHeight);
|
containerHeight);
|
||||||
} else {
|
} else {
|
||||||
return getCameraVideoPosition( width,
|
return getCameraVideoPosition( width,
|
||||||
height,
|
height,
|
||||||
containerWidth,
|
containerWidth,
|
||||||
containerHeight);
|
containerHeight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update position of the remote connection message which describes that
|
||||||
|
* the remote user is having connectivity issues.
|
||||||
|
*/
|
||||||
|
positionRemoteConnectionMessage () {
|
||||||
|
|
||||||
|
if (this.avatarDisplayed) {
|
||||||
|
let $avatarImage = $("#dominantSpeakerAvatar");
|
||||||
|
this.$remoteConnectionMessage.css(
|
||||||
|
'top',
|
||||||
|
$avatarImage.offset().top + $avatarImage.height() + 10);
|
||||||
|
} else {
|
||||||
|
let height = this.$remoteConnectionMessage.height();
|
||||||
|
let parentHeight = this.$remoteConnectionMessage.parent().height();
|
||||||
|
this.$remoteConnectionMessage.css(
|
||||||
|
'top', (parentHeight/2) - (height/2));
|
||||||
|
}
|
||||||
|
|
||||||
|
let width = this.$remoteConnectionMessage.width();
|
||||||
|
let parentWidth = this.$remoteConnectionMessage.parent().width();
|
||||||
|
this.$remoteConnectionMessage.css(
|
||||||
|
'left', ((parentWidth/2) - (width/2)));
|
||||||
|
}
|
||||||
|
|
||||||
resize (containerWidth, containerHeight, animate = false) {
|
resize (containerWidth, containerHeight, animate = false) {
|
||||||
let [width, height]
|
let [width, height]
|
||||||
= this.getVideoSize(containerWidth, containerHeight);
|
= this.getVideoSize(containerWidth, containerHeight);
|
||||||
let { horizontalIndent, verticalIndent }
|
let { horizontalIndent, verticalIndent }
|
||||||
= this.getVideoPosition(width, height,
|
= this.getVideoPosition(width, height,
|
||||||
containerWidth, containerHeight);
|
containerWidth, containerHeight);
|
||||||
|
|
||||||
// update avatar position
|
// update avatar position
|
||||||
let top = containerHeight / 2 - this.avatarHeight / 4 * 3;
|
let top = containerHeight / 2 - this.avatarHeight / 4 * 3;
|
||||||
|
|
||||||
this.$avatar.css('top', top);
|
this.$avatar.css('top', top);
|
||||||
|
|
||||||
|
this.positionRemoteConnectionMessage();
|
||||||
|
|
||||||
this.$wrapper.animate({
|
this.$wrapper.animate({
|
||||||
width: width,
|
width: width,
|
||||||
height: height,
|
height: height,
|
||||||
|
@ -272,6 +336,14 @@ class VideoContainer extends LargeContainer {
|
||||||
* @param {string} videoType video type
|
* @param {string} videoType video type
|
||||||
*/
|
*/
|
||||||
setStream (stream, videoType) {
|
setStream (stream, videoType) {
|
||||||
|
|
||||||
|
if (this.stream === stream) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
// The stream has changed, so the image will be lost on detach
|
||||||
|
this.wasVideoRendered = false;
|
||||||
|
}
|
||||||
|
|
||||||
// detach old stream
|
// detach old stream
|
||||||
if (this.stream) {
|
if (this.stream) {
|
||||||
this.stream.detach(this.$video[0]);
|
this.stream.detach(this.$video[0]);
|
||||||
|
@ -327,6 +399,21 @@ class VideoContainer extends LargeContainer {
|
||||||
(show) ? interfaceConfig.DEFAULT_BACKGROUND : "#000");
|
(show) ? interfaceConfig.DEFAULT_BACKGROUND : "#000");
|
||||||
|
|
||||||
this.$avatar.css("visibility", show ? "visible" : "hidden");
|
this.$avatar.css("visibility", show ? "visible" : "hidden");
|
||||||
|
this.avatarDisplayed = show;
|
||||||
|
|
||||||
|
this.emitter.emit(UIEvents.LARGE_VIDEO_AVATAR_DISPLAYED, show);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates that the remote user who is currently displayed by this video
|
||||||
|
* container is having connectivity issues.
|
||||||
|
*
|
||||||
|
* @param {boolean} show <tt>true</tt> to show or <tt>false</tt> to hide
|
||||||
|
* the indication.
|
||||||
|
*/
|
||||||
|
showRemoteConnectionProblemIndicator (show) {
|
||||||
|
this.$video.toggleClass("remoteVideoProblemFilter", show);
|
||||||
|
this.$avatar.toggleClass("remoteVideoProblemFilter", show);
|
||||||
}
|
}
|
||||||
|
|
||||||
// We are doing fadeOut/fadeIn animations on parent div which wraps
|
// We are doing fadeOut/fadeIn animations on parent div which wraps
|
||||||
|
@ -380,304 +467,3 @@ class VideoContainer extends LargeContainer {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Manager for all Large containers.
|
|
||||||
*/
|
|
||||||
export default class LargeVideoManager {
|
|
||||||
constructor () {
|
|
||||||
this.containers = {};
|
|
||||||
|
|
||||||
this.state = VIDEO_CONTAINER_TYPE;
|
|
||||||
this.videoContainer = new VideoContainer(
|
|
||||||
() => this.resizeContainer(VIDEO_CONTAINER_TYPE));
|
|
||||||
this.addContainer(VIDEO_CONTAINER_TYPE, this.videoContainer);
|
|
||||||
|
|
||||||
// use the same video container to handle and desktop tracks
|
|
||||||
this.addContainer("desktop", this.videoContainer);
|
|
||||||
|
|
||||||
this.width = 0;
|
|
||||||
this.height = 0;
|
|
||||||
|
|
||||||
this.$container = $('#largeVideoContainer');
|
|
||||||
|
|
||||||
this.$container.css({
|
|
||||||
display: 'inline-block'
|
|
||||||
});
|
|
||||||
|
|
||||||
if (interfaceConfig.SHOW_JITSI_WATERMARK) {
|
|
||||||
let leftWatermarkDiv
|
|
||||||
= this.$container.find("div.watermark.leftwatermark");
|
|
||||||
|
|
||||||
leftWatermarkDiv.css({display: 'block'});
|
|
||||||
|
|
||||||
leftWatermarkDiv.parent().attr(
|
|
||||||
'href', interfaceConfig.JITSI_WATERMARK_LINK);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (interfaceConfig.SHOW_BRAND_WATERMARK) {
|
|
||||||
let rightWatermarkDiv
|
|
||||||
= this.$container.find("div.watermark.rightwatermark");
|
|
||||||
|
|
||||||
rightWatermarkDiv.css({
|
|
||||||
display: 'block',
|
|
||||||
backgroundImage: 'url(images/rightwatermark.png)'
|
|
||||||
});
|
|
||||||
|
|
||||||
rightWatermarkDiv.parent().attr(
|
|
||||||
'href', interfaceConfig.BRAND_WATERMARK_LINK);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (interfaceConfig.SHOW_POWERED_BY) {
|
|
||||||
this.$container.children("a.poweredby").css({display: 'block'});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$container.hover(
|
|
||||||
e => this.onHoverIn(e),
|
|
||||||
e => this.onHoverOut(e)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
onHoverIn (e) {
|
|
||||||
if (!this.state) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let container = this.getContainer(this.state);
|
|
||||||
container.onHoverIn(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
onHoverOut (e) {
|
|
||||||
if (!this.state) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let container = this.getContainer(this.state);
|
|
||||||
container.onHoverOut(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
get id () {
|
|
||||||
let container = this.getContainer(this.state);
|
|
||||||
return container.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
scheduleLargeVideoUpdate () {
|
|
||||||
if (this.updateInProcess || !this.newStreamData) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.updateInProcess = true;
|
|
||||||
|
|
||||||
let container = this.getContainer(this.state);
|
|
||||||
|
|
||||||
// Include hide()/fadeOut only if we're switching between users
|
|
||||||
let preUpdate;
|
|
||||||
if (this.newStreamData.id != this.id) {
|
|
||||||
preUpdate = container.hide();
|
|
||||||
} else {
|
|
||||||
preUpdate = Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
preUpdate.then(() => {
|
|
||||||
let {id, stream, videoType, resolve} = this.newStreamData;
|
|
||||||
this.newStreamData = null;
|
|
||||||
|
|
||||||
console.info("hover in %s", id);
|
|
||||||
this.state = videoType;
|
|
||||||
let container = this.getContainer(this.state);
|
|
||||||
container.setStream(stream, videoType);
|
|
||||||
|
|
||||||
// change the avatar url on large
|
|
||||||
this.updateAvatar(Avatar.getAvatarUrl(id));
|
|
||||||
|
|
||||||
// If we the continer is VIDEO_CONTAINER_TYPE, we need to check
|
|
||||||
// its stream whether exist and is muted to set isVideoMuted
|
|
||||||
// in rest of the cases it is false
|
|
||||||
let isVideoMuted = false;
|
|
||||||
if (videoType == VIDEO_CONTAINER_TYPE)
|
|
||||||
isVideoMuted = stream ? stream.isMuted() : true;
|
|
||||||
|
|
||||||
// show the avatar on large if needed
|
|
||||||
container.showAvatar(isVideoMuted);
|
|
||||||
|
|
||||||
let promise;
|
|
||||||
|
|
||||||
// do not show stream if video is muted
|
|
||||||
// but we still should show watermark
|
|
||||||
if (isVideoMuted) {
|
|
||||||
this.showWatermark(true);
|
|
||||||
promise = Promise.resolve();
|
|
||||||
} else {
|
|
||||||
promise = container.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
// resolve updateLargeVideo promise after everything is done
|
|
||||||
promise.then(resolve);
|
|
||||||
|
|
||||||
return promise;
|
|
||||||
}).then(() => {
|
|
||||||
// after everything is done check again if there are any pending
|
|
||||||
// new streams.
|
|
||||||
this.updateInProcess = false;
|
|
||||||
this.scheduleLargeVideoUpdate();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update large video.
|
|
||||||
* Switches to large video even if previously other container was visible.
|
|
||||||
* @param userID the userID of the participant associated with the stream
|
|
||||||
* @param {JitsiTrack?} stream new stream
|
|
||||||
* @param {string?} videoType new video type
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
updateLargeVideo (userID, stream, videoType) {
|
|
||||||
if (this.newStreamData) {
|
|
||||||
this.newStreamData.reject();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.newStreamData = createDeferred();
|
|
||||||
this.newStreamData.id = userID;
|
|
||||||
this.newStreamData.stream = stream;
|
|
||||||
this.newStreamData.videoType = videoType;
|
|
||||||
|
|
||||||
this.scheduleLargeVideoUpdate();
|
|
||||||
|
|
||||||
return this.newStreamData.promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update container size.
|
|
||||||
*/
|
|
||||||
updateContainerSize () {
|
|
||||||
this.width = UIUtil.getAvailableVideoWidth();
|
|
||||||
this.height = window.innerHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resize Large container of specified type.
|
|
||||||
* @param {string} type type of container which should be resized.
|
|
||||||
* @param {boolean} [animate=false] if resize process should be animated.
|
|
||||||
*/
|
|
||||||
resizeContainer (type, animate = false) {
|
|
||||||
let container = this.getContainer(type);
|
|
||||||
container.resize(this.width, this.height, animate);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resize all Large containers.
|
|
||||||
* @param {boolean} animate if resize process should be animated.
|
|
||||||
*/
|
|
||||||
resize (animate) {
|
|
||||||
// resize all containers
|
|
||||||
Object.keys(this.containers)
|
|
||||||
.forEach(type => this.resizeContainer(type, animate));
|
|
||||||
|
|
||||||
this.$container.animate({
|
|
||||||
width: this.width,
|
|
||||||
height: this.height
|
|
||||||
}, {
|
|
||||||
queue: false,
|
|
||||||
duration: animate ? 500 : 0
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enables/disables the filter indicating a video problem to the user.
|
|
||||||
*
|
|
||||||
* @param enable <tt>true</tt> to enable, <tt>false</tt> to disable
|
|
||||||
*/
|
|
||||||
enableVideoProblemFilter (enable) {
|
|
||||||
let container = this.getContainer(this.state);
|
|
||||||
container.$video.toggleClass("videoProblemFilter", enable);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the src of the dominant speaker avatar
|
|
||||||
*/
|
|
||||||
updateAvatar (avatarUrl) {
|
|
||||||
$("#dominantSpeakerAvatar").attr('src', avatarUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show or hide watermark.
|
|
||||||
* @param {boolean} show
|
|
||||||
*/
|
|
||||||
showWatermark (show) {
|
|
||||||
$('.watermark').css('visibility', show ? 'visible' : 'hidden');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add container of specified type.
|
|
||||||
* @param {string} type container type
|
|
||||||
* @param {LargeContainer} container container to add.
|
|
||||||
*/
|
|
||||||
addContainer (type, container) {
|
|
||||||
if (this.containers[type]) {
|
|
||||||
throw new Error(`container of type ${type} already exist`);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.containers[type] = container;
|
|
||||||
this.resizeContainer(type);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get Large container of specified type.
|
|
||||||
* @param {string} type container type.
|
|
||||||
* @returns {LargeContainer}
|
|
||||||
*/
|
|
||||||
getContainer (type) {
|
|
||||||
let container = this.containers[type];
|
|
||||||
|
|
||||||
if (!container) {
|
|
||||||
throw new Error(`container of type ${type} doesn't exist`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return container;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove Large container of specified type.
|
|
||||||
* @param {string} type container type.
|
|
||||||
*/
|
|
||||||
removeContainer (type) {
|
|
||||||
if (!this.containers[type]) {
|
|
||||||
throw new Error(`container of type ${type} doesn't exist`);
|
|
||||||
}
|
|
||||||
|
|
||||||
delete this.containers[type];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show Large container of specified type.
|
|
||||||
* Does nothing if such container is already visible.
|
|
||||||
* @param {string} type container type.
|
|
||||||
* @returns {Promise}
|
|
||||||
*/
|
|
||||||
showContainer (type) {
|
|
||||||
if (this.state === type) {
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
let oldContainer = this.containers[this.state];
|
|
||||||
if (this.state === VIDEO_CONTAINER_TYPE) {
|
|
||||||
this.showWatermark(false);
|
|
||||||
}
|
|
||||||
oldContainer.hide();
|
|
||||||
|
|
||||||
this.state = type;
|
|
||||||
let container = this.getContainer(type);
|
|
||||||
|
|
||||||
return container.show().then(() => {
|
|
||||||
if (type === VIDEO_CONTAINER_TYPE) {
|
|
||||||
this.showWatermark(true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Changes the flipX state of the local video.
|
|
||||||
* @param val {boolean} true if flipped.
|
|
||||||
*/
|
|
||||||
onLocalFlipXChange(val) {
|
|
||||||
this.videoContainer.setLocalFlipX(val);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,14 +1,14 @@
|
||||||
/* global config, APP, $, interfaceConfig, JitsiMeetJS */
|
/* global config, APP, $, interfaceConfig, JitsiMeetJS */
|
||||||
/* jshint -W101 */
|
/* jshint -W101 */
|
||||||
|
|
||||||
import AudioLevels from "../audio_levels/AudioLevels";
|
|
||||||
import Avatar from "../avatar/Avatar";
|
import Avatar from "../avatar/Avatar";
|
||||||
import FilmStrip from "./FilmStrip";
|
import FilmStrip from "./FilmStrip";
|
||||||
import UIEvents from "../../../service/UI/UIEvents";
|
import UIEvents from "../../../service/UI/UIEvents";
|
||||||
import UIUtil from "../util/UIUtil";
|
import UIUtil from "../util/UIUtil";
|
||||||
|
|
||||||
import RemoteVideo from "./RemoteVideo";
|
import RemoteVideo from "./RemoteVideo";
|
||||||
import LargeVideoManager, {VIDEO_CONTAINER_TYPE} from "./LargeVideo";
|
import LargeVideoManager from "./LargeVideoManager";
|
||||||
|
import {VIDEO_CONTAINER_TYPE} from "./VideoContainer";
|
||||||
import {SHARED_VIDEO_CONTAINER_TYPE} from '../shared_video/SharedVideo';
|
import {SHARED_VIDEO_CONTAINER_TYPE} from '../shared_video/SharedVideo';
|
||||||
import LocalVideo from "./LocalVideo";
|
import LocalVideo from "./LocalVideo";
|
||||||
|
|
||||||
|
@ -102,32 +102,37 @@ var VideoLayout = {
|
||||||
});
|
});
|
||||||
localVideoThumbnail = new LocalVideo(VideoLayout, emitter);
|
localVideoThumbnail = new LocalVideo(VideoLayout, emitter);
|
||||||
// sets default video type of local video
|
// sets default video type of local video
|
||||||
|
// FIXME container type is totally different thing from the video type
|
||||||
localVideoThumbnail.setVideoType(VIDEO_CONTAINER_TYPE);
|
localVideoThumbnail.setVideoType(VIDEO_CONTAINER_TYPE);
|
||||||
// if we do not resize the thumbs here, if there is no video device
|
// if we do not resize the thumbs here, if there is no video device
|
||||||
// the local video thumb maybe one pixel
|
// the local video thumb maybe one pixel
|
||||||
let {thumbWidth, thumbHeight} = this.resizeThumbnails(false, true);
|
let { localVideo } = this.resizeThumbnails(false, true);
|
||||||
AudioLevels.updateAudioLevelCanvas(null, thumbWidth, thumbHeight);
|
|
||||||
|
|
||||||
emitter.addListener(UIEvents.CONTACT_CLICKED, onContactClicked);
|
emitter.addListener(UIEvents.CONTACT_CLICKED, onContactClicked);
|
||||||
this.lastNCount = config.channelLastN;
|
this.lastNCount = config.channelLastN;
|
||||||
},
|
},
|
||||||
|
|
||||||
initLargeVideo () {
|
initLargeVideo () {
|
||||||
largeVideo = new LargeVideoManager();
|
largeVideo = new LargeVideoManager(eventEmitter);
|
||||||
if(localFlipX) {
|
if(localFlipX) {
|
||||||
largeVideo.onLocalFlipXChange(localFlipX);
|
largeVideo.onLocalFlipXChange(localFlipX);
|
||||||
}
|
}
|
||||||
largeVideo.updateContainerSize();
|
largeVideo.updateContainerSize();
|
||||||
AudioLevels.init();
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the audio level of the video elements associated to the given id.
|
||||||
|
*
|
||||||
|
* @param id the video identifier in the form it comes from the library
|
||||||
|
* @param lvl the new audio level to update to
|
||||||
|
*/
|
||||||
setAudioLevel(id, lvl) {
|
setAudioLevel(id, lvl) {
|
||||||
if (!largeVideo) {
|
let smallVideo = this.getSmallVideo(id);
|
||||||
return;
|
if (smallVideo)
|
||||||
}
|
smallVideo.updateAudioLevelIndicator(lvl);
|
||||||
AudioLevels.updateAudioLevel(
|
|
||||||
id, lvl, largeVideo.id
|
if (largeVideo && id === largeVideo.id)
|
||||||
);
|
largeVideo.updateLargeVideoAudioLevel(lvl);
|
||||||
},
|
},
|
||||||
|
|
||||||
isInLastN (resource) {
|
isInLastN (resource) {
|
||||||
|
@ -254,7 +259,8 @@ var VideoLayout = {
|
||||||
electLastVisibleVideo () {
|
electLastVisibleVideo () {
|
||||||
// pick the last visible video in the row
|
// pick the last visible video in the row
|
||||||
// if nobody else is left, this picks the local video
|
// if nobody else is left, this picks the local video
|
||||||
let thumbs = FilmStrip.getThumbs(true).filter('[id!="mixedstream"]');
|
let remoteThumbs = FilmStrip.getThumbs(true).remoteThumbs;
|
||||||
|
let thumbs = remoteThumbs.filter('[id!="mixedstream"]');
|
||||||
|
|
||||||
let lastVisible = thumbs.filter(':visible:last');
|
let lastVisible = thumbs.filter(':visible:last');
|
||||||
if (lastVisible.length) {
|
if (lastVisible.length) {
|
||||||
|
@ -268,7 +274,7 @@ var VideoLayout = {
|
||||||
}
|
}
|
||||||
|
|
||||||
console.info("Last visible video no longer exists");
|
console.info("Last visible video no longer exists");
|
||||||
thumbs = FilmStrip.getThumbs();
|
thumbs = FilmStrip.getThumbs().remoteThumbs;
|
||||||
if (thumbs.length) {
|
if (thumbs.length) {
|
||||||
let id = getPeerContainerResourceId(thumbs[0]);
|
let id = getPeerContainerResourceId(thumbs[0]);
|
||||||
if (remoteVideos[id]) {
|
if (remoteVideos[id]) {
|
||||||
|
@ -378,34 +384,49 @@ var VideoLayout = {
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a participant container for the given id and smallVideo.
|
* Creates or adds a participant container for the given id and smallVideo.
|
||||||
*
|
*
|
||||||
* @param id the id of the participant to add
|
* @param {JitsiParticipant} user the participant to add
|
||||||
* @param {SmallVideo} smallVideo optional small video instance to add as a
|
* @param {SmallVideo} smallVideo optional small video instance to add as a
|
||||||
* remote video, if undefined RemoteVideo will be created
|
* remote video, if undefined <tt>RemoteVideo</tt> will be created
|
||||||
*/
|
*/
|
||||||
addParticipantContainer (id, smallVideo) {
|
addParticipantContainer (user, smallVideo) {
|
||||||
|
let id = user.getId();
|
||||||
let remoteVideo;
|
let remoteVideo;
|
||||||
if(smallVideo)
|
if(smallVideo)
|
||||||
remoteVideo = smallVideo;
|
remoteVideo = smallVideo;
|
||||||
else
|
else
|
||||||
remoteVideo = new RemoteVideo(id, VideoLayout, eventEmitter);
|
remoteVideo = new RemoteVideo(user, VideoLayout, eventEmitter);
|
||||||
|
this.addRemoteVideoContainer(id, remoteVideo);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds remote video container for the given id and <tt>SmallVideo</tt>.
|
||||||
|
*
|
||||||
|
* @param {string} the id of the video to add
|
||||||
|
* @param {SmallVideo} smallVideo the small video instance to add as a
|
||||||
|
* remote video
|
||||||
|
*/
|
||||||
|
addRemoteVideoContainer (id, remoteVideo) {
|
||||||
remoteVideos[id] = remoteVideo;
|
remoteVideos[id] = remoteVideo;
|
||||||
|
|
||||||
let videoType = VideoLayout.getRemoteVideoType(id);
|
let videoType = VideoLayout.getRemoteVideoType(id);
|
||||||
if (!videoType) {
|
if (!videoType) {
|
||||||
// make video type the default one (camera)
|
// make video type the default one (camera)
|
||||||
|
// FIXME container type is not a video type
|
||||||
videoType = VIDEO_CONTAINER_TYPE;
|
videoType = VIDEO_CONTAINER_TYPE;
|
||||||
}
|
}
|
||||||
remoteVideo.setVideoType(videoType);
|
remoteVideo.setVideoType(videoType);
|
||||||
|
|
||||||
// In case this is not currently in the last n we don't show it.
|
// In case this is not currently in the last n we don't show it.
|
||||||
if (localLastNCount && localLastNCount > 0 &&
|
if (localLastNCount && localLastNCount > 0 &&
|
||||||
FilmStrip.getThumbs().length >= localLastNCount + 2) {
|
FilmStrip.getThumbs().remoteThumbs.length >= localLastNCount + 2) {
|
||||||
remoteVideo.showPeerContainer('hide');
|
remoteVideo.showPeerContainer('hide');
|
||||||
} else {
|
} else {
|
||||||
VideoLayout.resizeThumbnails(false, true);
|
VideoLayout.resizeThumbnails(false, true);
|
||||||
}
|
}
|
||||||
|
// Initialize the view
|
||||||
|
remoteVideo.updateView();
|
||||||
},
|
},
|
||||||
|
|
||||||
videoactive (videoelem, resourceJid) {
|
videoactive (videoelem, resourceJid) {
|
||||||
|
@ -448,9 +469,9 @@ var VideoLayout = {
|
||||||
showModeratorIndicator () {
|
showModeratorIndicator () {
|
||||||
let isModerator = APP.conference.isModerator;
|
let isModerator = APP.conference.isModerator;
|
||||||
if (isModerator) {
|
if (isModerator) {
|
||||||
localVideoThumbnail.createModeratorIndicatorElement();
|
localVideoThumbnail.addModeratorIndicator();
|
||||||
} else {
|
} else {
|
||||||
localVideoThumbnail.removeModeratorIndicatorElement();
|
localVideoThumbnail.removeModeratorIndicator();
|
||||||
}
|
}
|
||||||
|
|
||||||
APP.conference.listMembers().forEach(function (member) {
|
APP.conference.listMembers().forEach(function (member) {
|
||||||
|
@ -460,9 +481,10 @@ var VideoLayout = {
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (member.isModerator()) {
|
if (member.isModerator()) {
|
||||||
remoteVideo.removeRemoteVideoMenu();
|
remoteVideo.addModeratorIndicator();
|
||||||
remoteVideo.createModeratorIndicatorElement();
|
}
|
||||||
} else if (isModerator) {
|
|
||||||
|
if (isModerator) {
|
||||||
// We are moderator, but user is not - add menu
|
// We are moderator, but user is not - add menu
|
||||||
if(!remoteVideo.hasRemoteVideoMenu) {
|
if(!remoteVideo.hasRemoteVideoMenu) {
|
||||||
remoteVideo.addRemoteVideoMenu();
|
remoteVideo.addRemoteVideoMenu();
|
||||||
|
@ -479,6 +501,18 @@ var VideoLayout = {
|
||||||
localVideoThumbnail.showAudioIndicator(isMuted);
|
localVideoThumbnail.showAudioIndicator(isMuted);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows/hides the indication about local connection being interrupted.
|
||||||
|
*
|
||||||
|
* @param {boolean} isInterrupted <tt>true</tt> if local connection is
|
||||||
|
* currently in the interrupted state or <tt>false</tt> if the connection
|
||||||
|
* is fine.
|
||||||
|
*/
|
||||||
|
showLocalConnectionInterrupted (isInterrupted) {
|
||||||
|
localVideoThumbnail.connectionIndicator
|
||||||
|
.updateConnectionStatusIndicator(!isInterrupted);
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resizes thumbnails.
|
* Resizes thumbnails.
|
||||||
*/
|
*/
|
||||||
|
@ -486,19 +520,18 @@ var VideoLayout = {
|
||||||
forceUpdate = false,
|
forceUpdate = false,
|
||||||
onComplete = null) {
|
onComplete = null) {
|
||||||
|
|
||||||
let {thumbWidth, thumbHeight}
|
let { localVideo, remoteVideo }
|
||||||
= FilmStrip.calculateThumbnailSize();
|
= FilmStrip.calculateThumbnailSize();
|
||||||
|
|
||||||
$('.userAvatar').css('left', (thumbWidth - thumbHeight) / 2);
|
let {thumbWidth, thumbHeight} = remoteVideo;
|
||||||
|
|
||||||
FilmStrip.resizeThumbnails(thumbWidth, thumbHeight,
|
FilmStrip.resizeThumbnails(localVideo, remoteVideo,
|
||||||
animate, forceUpdate)
|
animate, forceUpdate)
|
||||||
.then(function () {
|
.then(function () {
|
||||||
AudioLevels.updateCanvasSize(thumbWidth, thumbHeight);
|
|
||||||
if (onComplete && typeof onComplete === "function")
|
if (onComplete && typeof onComplete === "function")
|
||||||
onComplete();
|
onComplete();
|
||||||
});
|
});
|
||||||
return {thumbWidth, thumbHeight};
|
return { localVideo, remoteVideo };
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -524,11 +557,11 @@ var VideoLayout = {
|
||||||
*/
|
*/
|
||||||
onVideoMute (id, value) {
|
onVideoMute (id, value) {
|
||||||
if (APP.conference.isLocalId(id)) {
|
if (APP.conference.isLocalId(id)) {
|
||||||
localVideoThumbnail.setMutedView(value);
|
localVideoThumbnail.setVideoMutedView(value);
|
||||||
} else {
|
} else {
|
||||||
let remoteVideo = remoteVideos[id];
|
let remoteVideo = remoteVideos[id];
|
||||||
if (remoteVideo)
|
if (remoteVideo)
|
||||||
remoteVideo.setMutedView(value);
|
remoteVideo.setVideoMutedView(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isCurrentlyOnLarge(id)) {
|
if (this.isCurrentlyOnLarge(id)) {
|
||||||
|
@ -610,6 +643,35 @@ var VideoLayout = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows/hides warning about remote user's connectivity issues.
|
||||||
|
*
|
||||||
|
* @param {string} id the ID of the remote participant(MUC nickname)
|
||||||
|
* @param {boolean} isActive true if the connection is ok or false when
|
||||||
|
* the user is having connectivity issues.
|
||||||
|
*/
|
||||||
|
onParticipantConnectionStatusChanged (id, isActive) {
|
||||||
|
// Show/hide warning on the large video
|
||||||
|
if (this.isCurrentlyOnLarge(id)) {
|
||||||
|
if (largeVideo) {
|
||||||
|
// We have to trigger full large video update to transition from
|
||||||
|
// avatar to video on connectivity restored.
|
||||||
|
this.updateLargeVideo(id, true /* force update */);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Show/hide warning on the thumbnail
|
||||||
|
let remoteVideo = remoteVideos[id];
|
||||||
|
if (remoteVideo) {
|
||||||
|
// Updating only connection status indicator is not enough, because
|
||||||
|
// when we the connection is restored while the avatar was displayed
|
||||||
|
// (due to 'muted while disconnected' condition) we may want to show
|
||||||
|
// the video stream again and in order to do that the display mode
|
||||||
|
// must be updated.
|
||||||
|
//remoteVideo.updateConnectionStatusIndicator(isActive);
|
||||||
|
remoteVideo.updateView();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* On last N change event.
|
* On last N change event.
|
||||||
*
|
*
|
||||||
|
@ -656,7 +718,7 @@ var VideoLayout = {
|
||||||
var updateLargeVideo = false;
|
var updateLargeVideo = false;
|
||||||
|
|
||||||
// Handle LastN/local LastN changes.
|
// Handle LastN/local LastN changes.
|
||||||
FilmStrip.getThumbs().each(( index, element ) => {
|
FilmStrip.getThumbs().remoteThumbs.each(( index, element ) => {
|
||||||
var resourceJid = getPeerContainerResourceId(element);
|
var resourceJid = getPeerContainerResourceId(element);
|
||||||
var smallVideo = remoteVideos[resourceJid];
|
var smallVideo = remoteVideos[resourceJid];
|
||||||
|
|
||||||
|
@ -945,28 +1007,18 @@ var VideoLayout = {
|
||||||
* Indicates that the video has been interrupted.
|
* Indicates that the video has been interrupted.
|
||||||
*/
|
*/
|
||||||
onVideoInterrupted () {
|
onVideoInterrupted () {
|
||||||
this.enableVideoProblemFilter(true);
|
if (largeVideo) {
|
||||||
let reconnectingKey = "connection.RECONNECTING";
|
largeVideo.onVideoInterrupted();
|
||||||
$('#videoConnectionMessage')
|
}
|
||||||
.attr("data-i18n", reconnectingKey)
|
|
||||||
.text(APP.translation.translateString(reconnectingKey))
|
|
||||||
.css({display: "block"});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates that the video has been restored.
|
* Indicates that the video has been restored.
|
||||||
*/
|
*/
|
||||||
onVideoRestored () {
|
onVideoRestored () {
|
||||||
this.enableVideoProblemFilter(false);
|
if (largeVideo) {
|
||||||
$('#videoConnectionMessage').css({display: "none"});
|
largeVideo.onVideoRestored();
|
||||||
},
|
|
||||||
|
|
||||||
enableVideoProblemFilter (enable) {
|
|
||||||
if (!largeVideo) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
largeVideo.enableVideoProblemFilter(enable);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
isLargeVideoVisible () {
|
isLargeVideoVisible () {
|
||||||
|
@ -994,6 +1046,7 @@ var VideoLayout = {
|
||||||
|
|
||||||
if (!isOnLarge || forceUpdate) {
|
if (!isOnLarge || forceUpdate) {
|
||||||
let videoType = this.getRemoteVideoType(id);
|
let videoType = this.getRemoteVideoType(id);
|
||||||
|
// FIXME video type is not the same thing as container type
|
||||||
if (id !== currentId && videoType === VIDEO_CONTAINER_TYPE) {
|
if (id !== currentId && videoType === VIDEO_CONTAINER_TYPE) {
|
||||||
eventEmitter.emit(UIEvents.SELECTED_ENDPOINT, id);
|
eventEmitter.emit(UIEvents.SELECTED_ENDPOINT, id);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
var animateTimeout, updateTimeout;
|
var animateTimeout, updateTimeout;
|
||||||
|
|
||||||
var RoomnameGenerator = require("../../util/RoomnameGenerator");
|
var RoomnameGenerator = require("../../util/RoomnameGenerator");
|
||||||
|
import UIUtil from "../util/UIUtil";
|
||||||
|
|
||||||
function enter_room() {
|
function enter_room() {
|
||||||
var val = $("#enter_room_field").val();
|
var val = $("#enter_room_field").val();
|
||||||
|
@ -39,10 +40,10 @@ function setupWelcomePage() {
|
||||||
$("#welcome_page_header div[class='watermark leftwatermark']");
|
$("#welcome_page_header div[class='watermark leftwatermark']");
|
||||||
if(leftWatermarkDiv && leftWatermarkDiv.length > 0) {
|
if(leftWatermarkDiv && leftWatermarkDiv.length > 0) {
|
||||||
leftWatermarkDiv.css({display: 'block'});
|
leftWatermarkDiv.css({display: 'block'});
|
||||||
leftWatermarkDiv.parent().get(0).href =
|
UIUtil.setLinkHref(
|
||||||
interfaceConfig.JITSI_WATERMARK_LINK;
|
leftWatermarkDiv.parent(),
|
||||||
|
interfaceConfig.JITSI_WATERMARK_LINK);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (interfaceConfig.SHOW_BRAND_WATERMARK) {
|
if (interfaceConfig.SHOW_BRAND_WATERMARK) {
|
||||||
|
@ -50,8 +51,9 @@ function setupWelcomePage() {
|
||||||
$("#welcome_page_header div[class='watermark rightwatermark']");
|
$("#welcome_page_header div[class='watermark rightwatermark']");
|
||||||
if(rightWatermarkDiv && rightWatermarkDiv.length > 0) {
|
if(rightWatermarkDiv && rightWatermarkDiv.length > 0) {
|
||||||
rightWatermarkDiv.css({display: 'block'});
|
rightWatermarkDiv.css({display: 'block'});
|
||||||
rightWatermarkDiv.parent().get(0).href =
|
UIUtil.setLinkHref(
|
||||||
interfaceConfig.BRAND_WATERMARK_LINK;
|
rightWatermarkDiv.parent(),
|
||||||
|
interfaceConfig.BRAND_WATERMARK_LINK);
|
||||||
rightWatermarkDiv.get(0).style.backgroundImage =
|
rightWatermarkDiv.get(0).style.backgroundImage =
|
||||||
"url(images/rightwatermark.png)";
|
"url(images/rightwatermark.png)";
|
||||||
}
|
}
|
||||||
|
|
|
@ -74,13 +74,6 @@ var KeyboardShortcut = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
$('body').popover({ selector: '[data-toggle=popover]',
|
|
||||||
trigger: 'click hover',
|
|
||||||
content: function() {
|
|
||||||
return this.getAttribute("content")
|
|
||||||
+ self._getShortcutTooltip(this.getAttribute("shortcut"));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -128,7 +121,7 @@ var KeyboardShortcut = {
|
||||||
* or an empty string if the shortcutAttr is null, an empty string or not
|
* or an empty string if the shortcutAttr is null, an empty string or not
|
||||||
* found in the shortcut mapping
|
* found in the shortcut mapping
|
||||||
*/
|
*/
|
||||||
_getShortcutTooltip: function (shortcutAttr) {
|
getShortcutTooltip: function (shortcutAttr) {
|
||||||
if (typeof shortcutAttr === "string" && shortcutAttr.length > 0) {
|
if (typeof shortcutAttr === "string" && shortcutAttr.length > 0) {
|
||||||
for (var key in _shortcuts) {
|
for (var key in _shortcuts) {
|
||||||
if (_shortcuts.hasOwnProperty(key)
|
if (_shortcuts.hasOwnProperty(key)
|
||||||
|
|
|
@ -174,10 +174,12 @@ export default {
|
||||||
* Set device id of the camera which is currently in use.
|
* Set device id of the camera which is currently in use.
|
||||||
* Empty string stands for default device.
|
* Empty string stands for default device.
|
||||||
* @param {string} newId new camera device id
|
* @param {string} newId new camera device id
|
||||||
|
* @param {boolean} whether we need to store the value
|
||||||
*/
|
*/
|
||||||
setCameraDeviceId: function (newId = '') {
|
setCameraDeviceId: function (newId, store) {
|
||||||
cameraDeviceId = newId;
|
cameraDeviceId = newId;
|
||||||
window.localStorage.cameraDeviceId = newId;
|
if (store)
|
||||||
|
window.localStorage.cameraDeviceId = newId;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -192,10 +194,12 @@ export default {
|
||||||
* Set device id of the microphone which is currently in use.
|
* Set device id of the microphone which is currently in use.
|
||||||
* Empty string stands for default device.
|
* Empty string stands for default device.
|
||||||
* @param {string} newId new microphone device id
|
* @param {string} newId new microphone device id
|
||||||
|
* @param {boolean} whether we need to store the value
|
||||||
*/
|
*/
|
||||||
setMicDeviceId: function (newId = '') {
|
setMicDeviceId: function (newId, store) {
|
||||||
micDeviceId = newId;
|
micDeviceId = newId;
|
||||||
window.localStorage.micDeviceId = newId;
|
if (store)
|
||||||
|
window.localStorage.micDeviceId = newId;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
20
package.json
20
package.json
|
@ -16,23 +16,24 @@
|
||||||
"readmeFilename": "README.md",
|
"readmeFilename": "README.md",
|
||||||
"//": "Callstats.io does not work with recent versions of jsSHA (2.0.1 in particular)",
|
"//": "Callstats.io does not work with recent versions of jsSHA (2.0.1 in particular)",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@atlassian/aui": "^6.0.0",
|
||||||
"async": "0.9.0",
|
"async": "0.9.0",
|
||||||
"autosize": "^1.18.13",
|
"autosize": "^1.18.13",
|
||||||
"bootstrap": "3.1.1",
|
"bootstrap": "3.1.1",
|
||||||
"events": "*",
|
"events": "*",
|
||||||
"i18next-client": "1.7.7",
|
"i18next-client": "1.7.7",
|
||||||
"jquery": "~2.1.1",
|
|
||||||
"jQuery-Impromptu": "git+https://github.com/trentrichardson/jQuery-Impromptu.git#v6.0.0",
|
"jQuery-Impromptu": "git+https://github.com/trentrichardson/jQuery-Impromptu.git#v6.0.0",
|
||||||
"lib-jitsi-meet": "git+https://github.com/jitsi/lib-jitsi-meet.git",
|
"jquery": "~2.1.1",
|
||||||
"jquery-contextmenu": "*",
|
"jquery-contextmenu": "*",
|
||||||
"jquery-ui": "1.10.5",
|
"jquery-ui": "1.10.5",
|
||||||
"jssha": "1.5.0",
|
"jssha": "1.5.0",
|
||||||
|
"jws": "*",
|
||||||
|
"lib-jitsi-meet": "git+https://github.com/jitsi/lib-jitsi-meet.git",
|
||||||
|
"postis": "^2.2.0",
|
||||||
"retry": "0.6.1",
|
"retry": "0.6.1",
|
||||||
"strophe": "^1.2.2",
|
"strophe": "^1.2.2",
|
||||||
"strophejs-plugins": "^0.0.6",
|
"strophejs-plugins": "^0.0.6",
|
||||||
"toastr": "^2.0.3",
|
"toastr": "^2.0.3"
|
||||||
"postis": "^2.2.0",
|
|
||||||
"jws": "*"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel-polyfill": "*",
|
"babel-polyfill": "*",
|
||||||
|
@ -83,7 +84,11 @@
|
||||||
"tooltip": "./node_modules/bootstrap/js/tooltip.js",
|
"tooltip": "./node_modules/bootstrap/js/tooltip.js",
|
||||||
"popover": "./node_modules/bootstrap/js/popover.js",
|
"popover": "./node_modules/bootstrap/js/popover.js",
|
||||||
"jQuery-Impromptu": "./node_modules/jQuery-Impromptu/dist/jquery-impromptu.js",
|
"jQuery-Impromptu": "./node_modules/jQuery-Impromptu/dist/jquery-impromptu.js",
|
||||||
"autosize": "./node_modules/autosize/build/jquery.autosize.js"
|
"autosize": "./node_modules/autosize/build/jquery.autosize.js",
|
||||||
|
"aui": "./node_modules/@atlassian/aui/dist/aui/js/aui.js",
|
||||||
|
"aui-experimental": "./node_modules/@atlassian/aui/dist/aui/js/aui-experimental.js",
|
||||||
|
"aui-css": "./node_modules/@atlassian/aui/dist/aui/css/aui.min.css",
|
||||||
|
"aui-experimental-css": "./node_modules/@atlassian/aui/dist/aui/css/aui-experimental.min.css"
|
||||||
},
|
},
|
||||||
"browserify-shim": {
|
"browserify-shim": {
|
||||||
"jquery": [
|
"jquery": [
|
||||||
|
@ -109,6 +114,9 @@
|
||||||
"jQuery-Impromptu": {
|
"jQuery-Impromptu": {
|
||||||
"depends": "jquery:jQuery"
|
"depends": "jquery:jQuery"
|
||||||
},
|
},
|
||||||
|
"aui-experimental": {
|
||||||
|
"depends": "aui:AJS"
|
||||||
|
},
|
||||||
"jquery-contextmenu": {
|
"jquery-contextmenu": {
|
||||||
"depends": "jquery:jQuery"
|
"depends": "jquery:jQuery"
|
||||||
},
|
},
|
||||||
|
|
|
@ -60,7 +60,7 @@ local function verify_user(session, stanza)
|
||||||
|
|
||||||
local token = session.auth_token;
|
local token = session.auth_token;
|
||||||
local auth_room = session.jitsi_meet_room;
|
local auth_room = session.jitsi_meet_room;
|
||||||
if room ~= auth_room and disableRoomNameConstraints ~= true then
|
if disableRoomNameConstraints ~= true and room ~= string.lower(auth_room) then
|
||||||
log("error", "Token %s not allowed to join: %s",
|
log("error", "Token %s not allowed to join: %s",
|
||||||
tostring(token), tostring(auth_room));
|
tostring(token), tostring(auth_room));
|
||||||
session.send(
|
session.send(
|
||||||
|
|
|
@ -29,7 +29,6 @@ export default {
|
||||||
*/
|
*/
|
||||||
UPDATE_SHARED_VIDEO: "UI.update_shared_video",
|
UPDATE_SHARED_VIDEO: "UI.update_shared_video",
|
||||||
ROOM_LOCK_CLICKED: "UI.room_lock_clicked",
|
ROOM_LOCK_CLICKED: "UI.room_lock_clicked",
|
||||||
USER_INVITED: "UI.user_invited",
|
|
||||||
USER_KICKED: "UI.user_kicked",
|
USER_KICKED: "UI.user_kicked",
|
||||||
REMOTE_AUDIO_MUTED: "UI.remote_audio_muted",
|
REMOTE_AUDIO_MUTED: "UI.remote_audio_muted",
|
||||||
FULLSCREEN_TOGGLE: "UI.fullscreen_toggle",
|
FULLSCREEN_TOGGLE: "UI.fullscreen_toggle",
|
||||||
|
@ -105,5 +104,15 @@ export default {
|
||||||
* event must contain the identifier of the container that has been toggled
|
* event must contain the identifier of the container that has been toggled
|
||||||
* and information about toggle on or off.
|
* and information about toggle on or off.
|
||||||
*/
|
*/
|
||||||
SIDE_TOOLBAR_CONTAINER_TOGGLED: "UI.side_container_toggled"
|
SIDE_TOOLBAR_CONTAINER_TOGGLED: "UI.side_container_toggled",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies that the raise hand has been changed.
|
||||||
|
*/
|
||||||
|
LOCAL_RAISE_HAND_CHANGED: "UI.local_raise_hand_changed",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notifies that the avatar is displayed or not on the largeVideo.
|
||||||
|
*/
|
||||||
|
LARGE_VIDEO_AVATAR_DISPLAYED: "UI.large_video_avatar_displayed"
|
||||||
};
|
};
|
||||||
|
|
|
@ -9,16 +9,19 @@ module.exports = {
|
||||||
return languages;
|
return languages;
|
||||||
},
|
},
|
||||||
EN: "en",
|
EN: "en",
|
||||||
|
|
||||||
BG: "bg",
|
BG: "bg",
|
||||||
DE: "de",
|
DE: "de",
|
||||||
TR: "tr",
|
|
||||||
FR: "fr",
|
|
||||||
ES: "es",
|
ES: "es",
|
||||||
|
FR: "fr",
|
||||||
HY: "hy",
|
HY: "hy",
|
||||||
IT: "it",
|
IT: "it",
|
||||||
OC: "oc",
|
OC: "oc",
|
||||||
|
PL: "pl",
|
||||||
PTBR: "ptBR",
|
PTBR: "ptBR",
|
||||||
|
RU: "ru",
|
||||||
SK: "sk",
|
SK: "sk",
|
||||||
SL: "sl",
|
SL: "sl",
|
||||||
SV: "sv"
|
SV: "sv",
|
||||||
|
TR: "tr"
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue