Merge remote-tracking branch 'upstream/master'

This commit is contained in:
luciash 2016-04-20 10:30:24 +02:00
commit 2123820f9f
21 changed files with 578 additions and 202 deletions

6
app.js
View File

@ -52,7 +52,7 @@ const APP = {
handler: null
},
// Used for automated performance tests
performanceTimes: {
connectionTimes: {
"index.loaded": window.indexLoadedTime
},
UI,
@ -103,7 +103,7 @@ function obtainConfigAndInit() {
// Get config result callback
function(success, error) {
if (success) {
var now = APP.performanceTimes["configuration.fetched"] =
var now = APP.connectionTimes["configuration.fetched"] =
window.performance.now();
console.log("(TIME) configuration fetched:\t", now);
init();
@ -124,7 +124,7 @@ function obtainConfigAndInit() {
$(document).ready(function () {
var now = APP.performanceTimes["document.ready"] = window.performance.now();
var now = APP.connectionTimes["document.ready"] = window.performance.now();
console.log("(TIME) document ready:\t", now);
URLProcessor.setConfigParametersFromUrl();

View File

@ -21,16 +21,6 @@ const TrackErrors = JitsiMeetJS.errors.track;
let room, connection, localAudio, localVideo, roomLocker;
/**
* Known custom conference commands.
*/
const Commands = {
CONNECTION_QUALITY: "stats",
EMAIL: "email",
ETHERPAD: "etherpad",
SHARED_VIDEO: "shared-video"
};
import {VIDEO_CONTAINER_TYPE} from "./modules/UI/videolayout/LargeVideo";
/**
@ -52,10 +42,11 @@ function connect(roomName) {
/**
* Share email with other users.
* @param emailCommand the email command
* @param {string} email new email
*/
function sendEmail (email) {
room.sendCommand(Commands.EMAIL, {
function sendEmail (emailCommand, email) {
room.sendCommand(emailCommand, {
value: email,
attributes: {
id: room.myUserId()
@ -82,8 +73,11 @@ function getDisplayName (id) {
/**
* Mute or unmute local audio stream if it exists.
* @param {boolean} muted if audio stream should be muted or unmuted.
* @param {boolean} indicates if this local audio mute was a result of user
* interaction
*
*/
function muteLocalAudio (muted) {
function muteLocalAudio (muted, userInteraction) {
if (!localAudio) {
return;
}
@ -507,6 +501,61 @@ export default {
getLogs () {
return room.getLogs();
},
/**
* 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
* modules to send and receive commands to and from participants.
* Eventually, this instance remains in control with respect to the
* decision whether the Command(s) API of room (i.e. lib-jitsi-meet's
* JitsiConference) is to be used in the implementation of the Command(s)
* API of this instance.
*/
commands: {
/**
* Known custom conference commands.
*/
defaults: {
CONNECTION_QUALITY: "stats",
EMAIL: "email",
ETHERPAD: "etherpad",
SHARED_VIDEO: "shared-video",
CUSTOM_ROLE: "custom-role"
},
/**
* Receives notifications from other participants about commands aka
* custom events (sent by sendCommand or sendCommandOnce methods).
* @param command {String} the name of the command
* @param handler {Function} handler for the command
*/
addCommandListener () {
room.addCommandListener.apply(room, arguments);
},
/**
* Removes command.
* @param name {String} the name of the command.
*/
removeCommand () {
room.removeCommand.apply(room, arguments);
},
/**
* Sends command.
* @param name {String} the name of the command.
* @param values {Object} with keys and values that will be sent.
*/
sendCommand () {
room.sendCommand.apply(room, arguments);
},
/**
* Sends command one time.
* @param name {String} the name of the command.
* @param values {Object} with keys and values that will be sent.
*/
sendCommandOnce () {
room.sendCommandOnce.apply(room, arguments);
}
},
_createRoom (localTracks) {
room = connection.initJitsiConference(APP.conference.roomName,
this._getConferenceOptions());
@ -522,7 +571,7 @@ export default {
this._room = room; // FIXME do not use this
let email = APP.settings.getEmail();
email && sendEmail(email);
email && sendEmail(this.commands.defaults.EMAIL, email);
let nick = APP.settings.getDisplayName();
if (config.useNicks && !nick) {
@ -534,50 +583,6 @@ export default {
this._setupListeners();
},
/**
* 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
* modules to send and receive commands to and from participants.
* Eventually, this instance remains in control with respect to the
* decision whether the Command(s) API of room (i.e. lib-jitsi-meet's
* JitsiConference) is to be used in the implementation of the Command(s)
* API of this instance.
*/
commands: {
/**
* Receives notifications from other participants about commands aka
* custom events (sent by sendCommand or sendCommandOnce methods).
* @param command {String} the name of the command
* @param handler {Function} handler for the command
*/
addCommandListener () {
room.addCommandListener.apply(room, arguments);
},
/**
* Removes command.
* @param name {String} the name of the command.
*/
removeCommand () {
room.removeCommand.apply(room, arguments);
},
/**
* Sends command.
* @param name {String} the name of the command.
* @param values {Object} with keys and values that will be sent.
*/
sendCommand () {
room.sendCommand.apply(room, arguments);
},
/**
* Sends command one time.
* @param name {String} the name of the command.
* @param values {Object} with keys and values that will be sent.
*/
sendCommandOnce () {
room.sendCommandOnce.apply(room, arguments);
},
},
_getConferenceOptions() {
let options = config;
if(config.enableRecording && !config.recordingType) {
@ -916,7 +921,8 @@ export default {
APP.UI.updateLocalStats(percent, stats);
// send local stats to other users
room.sendCommandOnce(Commands.CONNECTION_QUALITY, {
room.sendCommandOnce(this.commands.defaults.CONNECTION_QUALITY,
{
children: ConnectionQuality.convertToMUCStats(stats),
attributes: {
xmlns: 'http://jitsi.org/jitmeet/stats'
@ -926,8 +932,9 @@ export default {
);
// listen to remote stats
room.addCommandListener(Commands.CONNECTION_QUALITY,(values, from) => {
ConnectionQuality.updateRemoteStats(from, values);
room.addCommandListener(this.commands.defaults.CONNECTION_QUALITY,
(values, from) => {
ConnectionQuality.updateRemoteStats(from, values);
});
ConnectionQuality.addListener(CQEvents.REMOTESTATS_UPDATED,
@ -935,7 +942,7 @@ export default {
APP.UI.updateRemoteStats(id, percent, stats);
});
room.addCommandListener(Commands.ETHERPAD, ({value}) => {
room.addCommandListener(this.commands.defaults.ETHERPAD, ({value}) => {
APP.UI.initEtherpad(value);
});
@ -948,9 +955,9 @@ export default {
APP.settings.setEmail(email);
APP.UI.setUserAvatar(room.myUserId(), email);
sendEmail(email);
sendEmail(this.commands.defaults.EMAIL, email);
});
room.addCommandListener(Commands.EMAIL, (data) => {
room.addCommandListener(this.commands.defaults.EMAIL, (data) => {
APP.UI.setUserAvatar(data.attributes.id, data.value);
});
@ -1095,8 +1102,8 @@ export default {
// send start and stop commands once, and remove any updates
// that had left
if (state === 'stop' || state === 'start' || state === 'playing') {
room.removeCommand(Commands.SHARED_VIDEO);
room.sendCommandOnce(Commands.SHARED_VIDEO, {
room.removeCommand(this.commands.defaults.SHARED_VIDEO);
room.sendCommandOnce(this.commands.defaults.SHARED_VIDEO, {
value: url,
attributes: {
state: state,
@ -1108,8 +1115,8 @@ export default {
else {
// in case of paused, in order to allow late users to join
// paused
room.removeCommand(Commands.SHARED_VIDEO);
room.sendCommand(Commands.SHARED_VIDEO, {
room.removeCommand(this.commands.defaults.SHARED_VIDEO);
room.sendCommand(this.commands.defaults.SHARED_VIDEO, {
value: url,
attributes: {
state: state,
@ -1120,7 +1127,7 @@ export default {
}
});
room.addCommandListener(
Commands.SHARED_VIDEO, ({value, attributes}, id) => {
this.commands.defaults.SHARED_VIDEO, ({value, attributes}, id) => {
if (attributes.state === 'stop') {
APP.UI.stopSharedVideo(id, attributes);

View File

@ -23,7 +23,7 @@ server {
# xmpp websockets
location /xmpp-websocket {
proxy_pass http://localhost:5280;
proxy_pass http://localhost:5280/xmpp-websocket;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";

View File

@ -146,6 +146,9 @@
<ul id="micMutedPopup" class="loginmenu">
<li data-i18n="[html]toolbar.micMutedPopup"></li>
</ul>
<ul id="unableToUnmutePopup" class="loginmenu">
<li data-i18n="[html]toolbar.unableToUnmutePopup"></li>
</ul>
</a>
<a class="button icon-camera" id="toolbar_button_camera" data-container="body" data-toggle="popover" data-placement="bottom" shortcut="toggleVideoPopover" data-i18n="[content]toolbar.videomute" content="Start / stop camera"></a>
<a class="button" id="toolbar_button_record" data-container="body" data-toggle="popover" data-placement="bottom" style="display: none"></a>
@ -228,7 +231,7 @@
</span>
</span>
</div>
<div id="chatspace" class="right-panel">
<div id="chatspace" class="right-panel" style="display:none;">
<div id="nickname">
<span data-i18n="chat.nickname.title"></span>
<form>
@ -245,13 +248,13 @@
</div>
</div>
</div>
<div id="contactlist" class="right-panel">
<div id="contactlist" class="right-panel" style="display:none;">
<div class="title">
<i class="icon-contactList"><span data-i18n="contactlist"></span></i>
</div>
<ul id="contacts"></ul>
</div>
<div id="settingsmenu" class="right-panel">
<div id="settingsmenu" class="right-panel" style="display:none;">
<div class="icon-settings" data-i18n="settings.title"></div>
<img id="avatar" src="images/avatar2.png"/>
<div class="arrow-up"></div>

View File

@ -8,7 +8,7 @@
"participant": "Participant",
"me": "me",
"speaker": "Speaker",
"defaultNickname": "ex. __name__",
"defaultNickname": "ex. Jane Pink",
"defaultLink": "e.g. __url__",
"welcomepage":{
"go": "GO",
@ -65,7 +65,8 @@
"logout": "Logout",
"dialpad": "Show dialpad",
"sharedVideoMutedPopup": "Your shared video has been muted so<br/>that you can talk to the other participants.",
"micMutedPopup": "Your microphone has been muted so that you<br/>would fully enjoy your shared video."
"micMutedPopup": "Your microphone has been muted so that you<br/>would fully enjoy your shared video.",
"unableToUnmutePopup": "You cannot un-mute while the shared video is on."
},
"bottomtoolbar": {
"chat": "Open / close chat",
@ -203,7 +204,7 @@
"userPassword": "user password",
"token": "token",
"tokenAuthFailed": "Failed to authenticate with XMPP server: invalid token",
"displayNameRequired": "Please enter your display name:",
"displayNameRequired": "Please enter your display name",
"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>!",
"feedbackQuestion": "How was your call?",

View File

@ -69,6 +69,21 @@ 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.
*
@ -89,7 +104,14 @@ var Feedback = {
// if callstats isn't enabled.
if (!APP.conference.isCallstatsEnabled())
return;
$("#feedbackButtonDiv").css("display", "block");
// 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();
});
@ -100,13 +122,22 @@ var Feedback = {
_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 APP.conference.isCallstatsEnabled();
return this.enabled && APP.conference.isCallstatsEnabled();
},
/**
* Opens the feedback window.

View File

@ -29,6 +29,7 @@ var JitsiPopover = require("./util/JitsiPopover");
var Feedback = require("./Feedback");
import FollowMe from "../FollowMe";
import Recorder from "../recorder/Recorder";
var eventEmitter = new EventEmitter();
UI.eventEmitter = eventEmitter;
@ -42,32 +43,34 @@ let followMeHandler;
* Prompt user for nickname.
*/
function promptDisplayName() {
let nickRequiredMsg = APP.translation.translateString("dialog.displayNameRequired");
let defaultNickMsg = APP.translation.translateString(
"defaultNickname", {name: "Jane Pink"}
);
let nickRequiredMsg
= APP.translation.translateString("dialog.displayNameRequired");
let defaultNickMsg = APP.translation.translateString("defaultNickname");
let message = `
<h2 data-i18n="dialog.displayNameRequired">${nickRequiredMsg}</h2>
<input name="displayName" type="text"
data-i18n="[placeholder]defaultNickname"
placeholder="${defaultNickMsg}" autofocus>`;
let buttonTxt = APP.translation.generateTranslationHTML("dialog.Ok");
let buttons = [{title: buttonTxt, value: "ok"}];
// Don't use a translation string, because we're too early in the call and
// the translation may not be initialised.
let buttons = {Ok:true};
messageHandler.openDialog(
null, message,
let dialog = messageHandler.openDialog(
null,
message,
true,
buttons,
function (e, v, m, f) {
if (v == "ok") {
e.preventDefault();
if (v) {
let displayName = f.displayName;
if (displayName) {
UI.inputDisplayNameHandler(displayName);
return true;
dialog.close();
return;
}
}
e.preventDefault();
},
function () {
let form = $.prompt.getPrompt();
@ -110,8 +113,8 @@ function setupToolbars() {
* @see https://developer.mozilla.org/en-US/docs/Web/API/Fullscreen_API
*/
function toggleFullScreen () {
let isNotFullScreen = !document.fullscreenElement && // alternative standard method
// alternative standard method
let isNotFullScreen = !document.fullscreenElement &&
!document.mozFullScreenElement && // current working methods
!document.webkitFullscreenElement &&
!document.msFullscreenElement;
@ -124,7 +127,8 @@ function toggleFullScreen () {
} else if (document.documentElement.mozRequestFullScreen) {
document.documentElement.mozRequestFullScreen();
} else if (document.documentElement.webkitRequestFullscreen) {
document.documentElement.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
document.documentElement
.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
}
} else {
if (document.exitFullscreen) {
@ -238,6 +242,11 @@ UI.initConference = function () {
//if local role changes buttons state will be again updated
UI.updateLocalRole(false);
// Initialise the recorder handler. We're doing this explicitly before
// calling showToolbar, because the recorder may want to disable all
// toolbars.
new Recorder(APP.conference);
// Once we've joined the muc show the toolbar
ToolbarToggler.showToolbar();
@ -251,6 +260,7 @@ UI.initConference = function () {
UI.setUserAvatar(id, Settings.getEmail());
Toolbar.checkAutoEnableDesktopSharing();
if(!interfaceConfig.filmStripOnly) {
Feedback.init(eventEmitter);
}
@ -322,6 +332,14 @@ function bindEvents() {
$(window).resize(onResize);
}
/**
* Returns the shared document manager object.
* @return {EtherpadManager} the shared document manager object
*/
UI.getSharedVideoManager = function () {
return sharedVideoManager;
};
/**
* Starts the UI module and initializes all related components.
*
@ -528,6 +546,10 @@ UI.addUser = function (id, displayName) {
// Configure avatar
UI.setUserAvatar(id);
// set initial display name
if(displayName)
UI.changeDisplayName(id, displayName);
};
/**

View File

@ -133,6 +133,13 @@ function _requestRecordingToken () {
});
}
/**
* Shows a prompt dialog to the user when they have toggled off the recording.
*
* @param recordingType the recording type
* @returns {Promise}
* @private
*/
function _showStopRecordingPrompt (recordingType) {
var title;
var message;
@ -167,6 +174,12 @@ function _showStopRecordingPrompt (recordingType) {
});
}
/**
* Moves the element given by {selector} to the top right corner of the screen.
* @param selector the selector for the element to move
* @param move {true} to move the element, {false} to move it back to its intial
* position
*/
function moveToCorner(selector, move) {
let moveToCornerClass = "moveToCorner";
@ -176,6 +189,12 @@ function moveToCorner(selector, move) {
selector.removeClass(moveToCornerClass);
}
/**
* The status of the recorder.
* FIXME: Those constants should come from the library.
* @type {{ON: string, OFF: string, AVAILABLE: string,
* UNAVAILABLE: string, PENDING: string}}
*/
var Status = {
ON: "on",
OFF: "off",
@ -184,6 +203,11 @@ var Status = {
PENDING: "pending"
};
/**
* Manages the recording user interface and user experience.
* @type {{init, initRecordingButton, showRecordingButton, updateRecordingState,
* setRecordingButtonState, checkAutoRecord}}
*/
var Recording = {
/**
* Initializes the recording UI.
@ -196,6 +220,9 @@ var Recording = {
this.initRecordingButton(recordingType);
},
/**
* Initialise the recording button.
*/
initRecordingButton(recordingType) {
let selector = $('#toolbar_button_record');
@ -261,7 +288,10 @@ var Recording = {
});
},
// Shows or hides the 'recording' button.
/**
* Shows or hides the 'recording' button.
* @param show {true} to show the recording button, {false} to hide it
*/
showRecordingButton (show) {
if (_isRecordingButtonEnabled() && show) {
$('#toolbar_button_record').css({display: "inline-block"});
@ -270,6 +300,10 @@ var Recording = {
}
},
/**
* Updates the recording state UI.
* @param recordingState gives us the current recording state
*/
updateRecordingState(recordingState) {
// I'm the recorder, so I don't want to see any UI related to states.
if (config.iAmRecorder)
@ -282,7 +316,10 @@ var Recording = {
this.setRecordingButtonState(recordingState);
},
// Sets the state of the recording button
/**
* Sets the state of the recording button.
* @param recordingState gives us the current recording state
*/
setRecordingButtonState (recordingState) {
let buttonSelector = $('#toolbar_button_record');
let labelSelector = $('#recordingLabel');

View File

@ -26,6 +26,24 @@ export default class SharedVideoManager {
this.emitter = emitter;
this.isSharedVideoShown = false;
this.isPlayerAPILoaded = false;
this.mutedWithUserInteraction = false;
}
/**
* Indicates if the player volume is currently on.
*
* @returns {*|player|boolean}
*/
isSharedVideoVolumeOn() {
return (this.player && this.player.getVolume() > 0);
}
/**
* Indicates if the local user is the owner of the shared video.
* @returns {*|boolean}
*/
isSharedVideoOwner() {
return this.from && APP.conference.isLocalId(this.from);
}
/**
@ -169,10 +187,15 @@ export default class SharedVideoManager {
// let's check, if player is not muted lets mute locally
if(event.data.volume > 0 && !event.data.muted
&& !APP.conference.isLocalAudioMuted()){
self.emitter.emit(UIEvents.AUDIO_MUTED, true);
&& !APP.conference.isLocalAudioMuted()) {
self.emitter.emit(UIEvents.AUDIO_MUTED, true, false);
self.showMicMutedPopup(true);
}
else if (!self.mutedWithUserInteraction
&& (event.data.volume <=0 || event.data.muted)
&& APP.conference.isLocalAudioMuted()) {
self.emitter.emit(UIEvents.AUDIO_MUTED, false, false);
}
};
window.onPlayerReady = function(event) {
@ -231,9 +254,11 @@ export default class SharedVideoManager {
this.processTime(player, attributes, playerPaused);
// lets check the volume
if (attributes.volume !== undefined &&
player.getVolume() != attributes.volume
&& APP.conference.isLocalAudioMuted()) {
if (attributes.volume !== undefined
&& player.getVolume() != attributes.volume
&& (APP.conference.isLocalAudioMuted()
|| !this.mutedWithUserInteraction)) {
player.setVolume(attributes.volume);
console.info("Player change of volume:" + attributes.volume);
this.showSharedVideoMutedPopup(false);
@ -393,17 +418,21 @@ export default class SharedVideoManager {
/**
* Receives events for local audio mute/unmute by local user.
* @param muted boolena whether it is muted or not.
* @param {boolean} indicates if this mute was a result of user interaction,
* i.e. pressing the mute button or it was programatically triggerred
*/
localAudioMuted (muted) {
localAudioMuted (muted, userInteraction) {
if(!this.player)
return;
if(muted)
if (muted) {
this.mutedWithUserInteraction = userInteraction;
return;
}
// if we are un-muting and player is not muted, lets muted
// to not pollute the conference
if(this.player.getVolume() > 0 || !this.player.isMuted()){
if (this.player.getVolume() > 0 || !this.player.isMuted()) {
this.player.setVolume(0);
this.showSharedVideoMutedPopup(true);
}
@ -415,30 +444,10 @@ export default class SharedVideoManager {
* @param show boolean, show or hide the notification
*/
showMicMutedPopup (show) {
var micMutedPopupSelector = $("#micMutedPopup");
if(show) {
if(show)
this.showSharedVideoMutedPopup(false);
if (!micMutedPopupSelector.is(":visible"))
micMutedPopupSelector.css("display", "inline-block");
// FIXME: we need an utility method for that.
micMutedPopupSelector.fadeIn(300,
() => {micMutedPopupSelector.css({opacity: 1});}
);
setTimeout(
function () {
micMutedPopupSelector.fadeOut(300,
() => {micMutedPopupSelector.css({opacity: 0});}
);
}, 5000);
}
else {
micMutedPopupSelector.fadeOut(300,
() => {micMutedPopupSelector.css({opacity: 0});}
);
}
UIUtil.animateShowElement($("#micMutedPopup"), show, 5000);
}
/**
@ -448,30 +457,10 @@ export default class SharedVideoManager {
* @param show boolean, show or hide the notification
*/
showSharedVideoMutedPopup (show) {
var sharedVideoMutedPopupSelector = $("#sharedVideoMutedPopup");
if(show) {
if(show)
this.showMicMutedPopup(false);
if (!sharedVideoMutedPopupSelector.is(":visible"))
sharedVideoMutedPopupSelector.css("display", "inline-block");
// FIXME: we need an utility method for that.
sharedVideoMutedPopupSelector.fadeIn(300,
() => {sharedVideoMutedPopupSelector.css({opacity: 1});}
);
setTimeout(
function () {
sharedVideoMutedPopupSelector.fadeOut(300,
() => {sharedVideoMutedPopupSelector.css({opacity: 0});}
);
}, 5000);
}
else {
sharedVideoMutedPopupSelector.fadeOut(300,
() => {sharedVideoMutedPopupSelector.css({opacity: 0});}
);
}
UIUtil.animateShowElement($("#sharedVideoMutedPopup"), show, 5000);
}
}

View File

@ -315,7 +315,7 @@ var Chat = {
* Indicates if the chat is currently visible.
*/
isVisible () {
return $('#chatspace').is(":visible");
return UIUtil.isVisible(document.getElementById("chatspace"));
},
/**
* Shows and hides the window with the smileys

View File

@ -1,6 +1,7 @@
/* global $, APP */
import Avatar from '../../avatar/Avatar';
import UIEvents from '../../../../service/UI/UIEvents';
import UIUtil from '../../util/UIUtil';
let numberOfContacts = 0;
let notificationInterval;
@ -87,7 +88,7 @@ var ContactList = {
* otherwise
*/
isVisible () {
return $('#contactlist').is(":visible");
return UIUtil.isVisible(document.getElementById("contactlist"));
},
/**
@ -152,12 +153,14 @@ var ContactList = {
},
onDisplayNameChange (id, displayName) {
if(!displayName)
return;
if (id === 'localVideoContainer') {
id = APP.conference.localId;
}
let contactName = $(`#contacts #${id}>p`);
if (displayName) {
if (contactName.text() !== displayName) {
contactName.text(displayName);
}
},

View File

@ -161,7 +161,7 @@ export default {
* @returns {boolean}
*/
isVisible () {
return $('#settingsmenu').is(':visible');
return UIUtil.isVisible(document.getElementById("settingsmenu"));
},
/**

View File

@ -12,8 +12,27 @@ const defaultBottomToolbarButtons = {
const BottomToolbar = {
init () {
this.toolbar = $('#bottomToolbar');
},
// The bottom toolbar is enabled by default.
this.enabled = true;
},
/**
* Enables / disables the bottom toolbar.
* @param {e} set to {true} to enable the bottom toolbar or {false}
* to disable it
*/
enable (e) {
this.enabled = e;
if (!e && this.isVisible())
this.hide(false);
},
/**
* Indicates if the bottom toolbar is currently enabled.
* @return {this.enabled}
*/
isEnabled() {
return this.enabled;
},
setupListeners (emitter) {
UIUtil.hideDisabledButtons(defaultBottomToolbarButtons);

View File

@ -44,12 +44,25 @@ function openLinkDialog () {
const buttonHandlers = {
"toolbar_button_mute": function () {
let sharedVideoManager = APP.UI.getSharedVideoManager();
if (APP.conference.audioMuted) {
AnalyticsAdapter.sendEvent('toolbar.audio.unmuted');
emitter.emit(UIEvents.AUDIO_MUTED, false);
// If there's a shared video with the volume "on" and we aren't
// the video owner, we warn the user
// that currently it's not possible to unmute.
if (sharedVideoManager
&& sharedVideoManager.isSharedVideoVolumeOn()
&& !sharedVideoManager.isSharedVideoOwner()) {
UIUtil.animateShowElement(
$("#unableToUnmutePopup"), true, 5000);
}
else {
AnalyticsAdapter.sendEvent('toolbar.audio.unmuted');
emitter.emit(UIEvents.AUDIO_MUTED, false, true);
}
} else {
AnalyticsAdapter.sendEvent('toolbar.audio.muted');
emitter.emit(UIEvents.AUDIO_MUTED, true);
emitter.emit(UIEvents.AUDIO_MUTED, true, true);
}
},
"toolbar_button_camera": function () {
@ -171,6 +184,9 @@ function showSipNumberInput () {
const Toolbar = {
init (eventEmitter) {
emitter = eventEmitter;
// The toolbar is enabled by default.
this.enabled = true;
this.toolbarSelector = $("#header");
UIUtil.hideDisabledButtons(defaultToolbarButtons);
@ -178,14 +194,31 @@ const Toolbar = {
buttonId => $(`#${buttonId}`).click(buttonHandlers[buttonId])
);
},
/**
* Enables / disables the toolbar.
* @param {e} set to {true} to enable the toolbar or {false}
* to disable it
*/
enable (e) {
this.enabled = e;
if (!e && this.isVisible())
this.hide(false);
},
/**
* Indicates if the bottom toolbar is currently enabled.
* @return {this.enabled}
*/
isEnabled() {
return this.enabled;
},
/**
* Updates the room invite url.
*/
updateRoomUrl (newRoomUrl) {
roomUrl = newRoomUrl;
// If the invite dialog has been already opened we update the information.
// If the invite dialog has been already opened we update the
// information.
let inviteLink = document.getElementById('inviteLinkRef');
if (inviteLink) {
inviteLink.value = roomUrl;
@ -244,14 +277,16 @@ const Toolbar = {
// checks whether desktop sharing is enabled and whether
// we have params to start automatically sharing
checkAutoEnableDesktopSharing () {
if (UIUtil.isButtonEnabled('desktop') && config.autoEnableDesktopSharing) {
if (UIUtil.isButtonEnabled('desktop')
&& config.autoEnableDesktopSharing) {
emitter.emit(UIEvents.TOGGLE_SCREENSHARING);
}
},
// Shows or hides SIP calls button
showSipCallButton (show) {
if (APP.conference.sipGatewayEnabled() && UIUtil.isButtonEnabled('sip') && show) {
if (APP.conference.sipGatewayEnabled()
&& UIUtil.isButtonEnabled('sip') && show) {
$('#toolbar_button_sip').css({display: "inline-block"});
} else {
$('#toolbar_button_sip').css({display: "none"});
@ -331,7 +366,51 @@ const Toolbar = {
* @param {boolean} muted if icon should look like muted or not
*/
markAudioIconAsMuted (muted) {
$('#toolbar_button_mute').toggleClass("icon-microphone", !muted).toggleClass("icon-mic-disabled", muted);
$('#toolbar_button_mute').toggleClass("icon-microphone",
!muted).toggleClass("icon-mic-disabled", muted);
},
/**
* Indicates if the toolbar is currently hovered.
* @return {true} if the toolbar is currently hovered, {false} otherwise
*/
isHovered() {
this.toolbarSelector.find('*').each(function () {
let id = $(this).attr('id');
if ($(`#${id}:hover`).length > 0) {
return true;
}
});
if ($("#bottomToolbar:hover").length > 0) {
return true;
}
return false;
},
/**
* Returns true if this toolbar is currently visible, or false otherwise.
* @return <tt>true</tt> if currently visible, <tt>false</tt> - otherwise
*/
isVisible() {
return this.toolbarSelector.is(":visible");
},
/**
* Hides the toolbar with animation or not depending on the animate
* parameter.
*/
hide() {
this.toolbarSelector.hide(
"slide", { direction: "up", duration: 300});
},
/**
* Shows the toolbar with animation or not depending on the animate
* parameter.
*/
show() {
this.toolbarSelector.show(
"slide", { direction: "up", duration: 300});
}
};

View File

@ -2,6 +2,7 @@
import UIUtil from '../util/UIUtil';
import BottomToolbar from './BottomToolbar';
import Toolbar from './Toolbar';
import FilmStrip from '../videolayout/FilmStrip.js';
let toolbarTimeoutObject;
@ -16,10 +17,6 @@ function showDesktopSharingButton() {
}
}
function isToolbarVisible () {
return $('#header').is(':visible');
}
/**
* Hides the toolbar.
*/
@ -28,25 +25,13 @@ function hideToolbar() {
return;
}
let header = $("#header");
let isToolbarHover = false;
header.find('*').each(function () {
let id = $(this).attr('id');
if ($(`#${id}:hover`).length > 0) {
isToolbarHover = true;
}
});
if ($("#bottomToolbar:hover").length > 0) {
isToolbarHover = true;
}
clearTimeout(toolbarTimeoutObject);
toolbarTimeoutObject = null;
if (isToolbarHover) {
if (Toolbar.isHovered()) {
toolbarTimeoutObject = setTimeout(hideToolbar, toolbarTimeout);
} else {
header.hide("slide", { direction: "up", duration: 300});
Toolbar.hide();
$('#subject').animate({top: "-=40"}, 300);
if (!FilmStrip.isFilmStripVisible()) {
BottomToolbar.hide(true);
@ -59,18 +44,23 @@ const ToolbarToggler = {
* Shows the main toolbar.
*/
showToolbar () {
// if we are a recorder we do not want to show the toolbar
if (interfaceConfig.filmStripOnly || config.iAmRecorder) {
if (interfaceConfig.filmStripOnly) {
return;
}
let header = $("#header");
if (!header.is(':visible') || !BottomToolbar.isVisible()) {
header.show("slide", { direction: "up", duration: 300});
$('#subject').animate({top: "+=40"}, 300);
if (!BottomToolbar.isVisible()) {
BottomToolbar.show(true);
}
var updateTimeout = false;
if (Toolbar.isEnabled() && !Toolbar.isVisible()) {
Toolbar.show();
$('#subject').animate({top: "+=40"}, 300);
updateTimeout = true;
}
if (BottomToolbar.isEnabled() && !BottomToolbar.isVisible()) {
BottomToolbar.show(true);
updateTimeout = true;
}
if (updateTimeout) {
if (toolbarTimeoutObject) {
clearTimeout(toolbarTimeoutObject);
toolbarTimeoutObject = null;
@ -89,13 +79,13 @@ const ToolbarToggler = {
* @param isDock indicates what operation to perform
*/
dockToolbar (isDock) {
if (interfaceConfig.filmStripOnly) {
if (interfaceConfig.filmStripOnly || !Toolbar.isEnabled()) {
return;
}
if (isDock) {
// First make sure the toolbar is shown.
if (!isToolbarVisible()) {
if (!Toolbar.isVisible()) {
this.showToolbar();
}
@ -103,7 +93,7 @@ const ToolbarToggler = {
clearTimeout(toolbarTimeoutObject);
toolbarTimeoutObject = null;
} else {
if (isToolbarVisible()) {
if (Toolbar.isVisible()) {
toolbarTimeoutObject = setTimeout(hideToolbar, toolbarTimeout);
} else {
this.showToolbar();

View File

@ -152,7 +152,51 @@
return Object.keys(attrs).map(
key => ` ${key}="${attrs[key]}"`
).join(' ');
}
},
/**
* Checks if the given DOM element is currently visible. The offsetParent
* will be null if the "display" property of the element or any of its
* parent containers is set to "none". This method will NOT check the
* visibility property though.
* @param {el} The DOM element we'd like to check for visibility
*/
isVisible(el) {
return (el.offsetParent !== null);
},
/**
* Shows / hides the element given by {selector} and sets a timeout if the
* {hideDelay} is set to a value > 0.
* @param selector the jquery selector of the element to show/hide.
* @param show a {boolean} that indicates if the element should be shown or
* hidden
* @param hideDelay the value in milliseconds to wait before hiding the
* element
*/
animateShowElement(selector, show, hideDelay) {
if(show) {
if (!selector.is(":visible"))
selector.css("display", "inline-block");
selector.fadeIn(300,
() => {selector.css({opacity: 1});}
);
if (hideDelay && hideDelay > 0)
setTimeout(
function () {
selector.fadeOut(300,
() => {selector.css({opacity: 0});}
);
}, hideDelay);
}
else {
selector.fadeOut(300,
() => {selector.css({opacity: 0});}
);
}
}
};
export default UIUtil;

View File

@ -270,6 +270,17 @@ ConnectionIndicator.prototype.create = function () {
APP.translation.translateString("connectionindicator.na") + "</div>",
skin: "black"});
// override popover show method to make sure we will update the content
// before showing the popover
var origShowFunc = this.popover.show;
this.popover.show = function () {
// update content by forcing it, to finish even if popover
// is not visible
this.updatePopoverData(true);
// call the original show, passing its actual this
origShowFunc.call(this.popover);
}.bind(this);
this.emptyIcon = this.connectionIndicatorContainer.appendChild(
createIcon(["connection", "connection_empty"]));
this.fullIcon = this.connectionIndicatorContainer.appendChild(
@ -335,13 +346,18 @@ ConnectionIndicator.prototype.updateResolution = function (resolution) {
};
/**
* Updates the content of the popover
* Updates the content of the popover if its visible
* @param force to work even if popover is not visible
*/
ConnectionIndicator.prototype.updatePopoverData = function () {
this.popover.updateContent(
`<div class="connection_info">${this.generateText()}</div>`
);
APP.translation.translateElement($(".connection_info"));
ConnectionIndicator.prototype.updatePopoverData = function (force) {
// generate content, translate it and add it to document only if
// popover is visible or we force to do so.
if(this.popover.popoverShown || force) {
this.popover.updateContent(
`<div class="connection_info">${this.generateText()}</div>`
);
APP.translation.translateElement($(".connection_info"));
}
};
/**

View File

@ -357,7 +357,9 @@ RemoteVideo.prototype.setDisplayName = function(displayName, key) {
// If we already have a display name for this video.
if (nameSpan.length > 0) {
if (displayName && displayName.length > 0) {
$('#' + this.videoSpanId + '_name').text(displayName);
var displaynameSpan = $('#' + this.videoSpanId + '_name');
if (displaynameSpan.text() !== displayName)
displaynameSpan.text(displayName);
}
else if (key && key.length > 0) {
var nameHtml = APP.translation.generateTranslationHTML(key);

View File

@ -50,7 +50,25 @@ SmallVideo.prototype.showDisplayName = function(isShow) {
}
};
/**
* Enables / disables the device availability icons for this small video.
* @param {enable} set to {true} to enable and {false} to disable
*/
SmallVideo.prototype.enableDeviceAvailabilityIcons = function (enable) {
if (typeof enable === "undefined")
return;
this.deviceAvailabilityIconsEnabled = enable;
};
/**
* Sets the device "non" availability icons.
* @param devices the devices, which will be checked for availability
*/
SmallVideo.prototype.setDeviceAvailabilityIcons = function (devices) {
if (!this.deviceAvailabilityIconsEnabled)
return;
if(!this.container)
return;
@ -141,9 +159,10 @@ SmallVideo.createStreamElement = function (stream) {
element.id = SmallVideo.getStreamElementID(stream);
element.onplay = function () {
var now = APP.performanceTimes["video.render"]
var type = (isVideo ? 'video' : 'audio');
var now = APP.connectionTimes[type + ".render"]
= window.performance.now();
console.log("(TIME) Render " + (isVideo ? 'video' : 'audio') + ":\t",
console.log("(TIME) Render " + type + ":\t",
now);
};

View File

@ -196,6 +196,25 @@ var VideoLayout = {
video.setDeviceAvailabilityIcons(devices);
},
/**
* Enables/disables device availability icons for the given participant id.
* The default value is {true}.
* @param id the identifier of the participant
* @param enable {true} to enable device availability icons
*/
enableDeviceAvailabilityIcons (id, enable) {
let video;
if (APP.conference.isLocalId(id)) {
video = localVideoThumbnail;
}
else if (remoteVideos[id]) {
video = remoteVideos[id];
}
if (video)
video.enableDeviceAvailabilityIcons(enable);
},
/**
* Checks if removed video is currently displayed and tries to display
* another one instead.

View File

@ -0,0 +1,95 @@
/* global config, APP */
/*
* Copyright @ 2015 Atlassian Pty Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import VideoLayout from '../UI/videolayout/VideoLayout';
import Feedback from '../UI/Feedback.js';
import Toolbar from '../UI/toolbars/Toolbar';
import BottomToolbar from '../UI/toolbars/BottomToolbar';
const _RECORDER_CUSTOM_ROLE = "recorder-role";
class Recorder {
/**
* Initializes a new {Recorder} instance.
*
* @param conference the {conference} which is to transport
* {Recorder}-related information between participants
*/
constructor (conference) {
this._conference = conference;
// If I am a recorder then I publish my recorder custom role to notify
// everyone.
if (config.iAmRecorder) {
VideoLayout.enableDeviceAvailabilityIcons(conference.localId, true);
this._publishMyRecorderRole();
Feedback.enableFeedback(false);
Toolbar.enable(false);
BottomToolbar.enable(false);
}
// Listen to "CUSTOM_ROLE" commands.
this._conference.commands.addCommandListener(
this._conference.commands.defaults.CUSTOM_ROLE,
this._onCustomRoleCommand.bind(this));
}
/**
* Publish the recorder custom role.
* @private
*/
_publishMyRecorderRole () {
var conference = this._conference;
var commands = conference.commands;
commands.removeCommand(commands.defaults.CUSTOM_ROLE);
var self = this;
commands.sendCommandOnce(
commands.defaults.CUSTOM_ROLE,
{
attributes: {
recorderRole: true
}
});
}
/**
* Notifies this instance about a &qout;Custom Role&qout; command (delivered
* by the Command(s) API of {this._conference}).
*
* @param attributes the attributes {Object} carried by the command
* @param id the identifier of the participant who issued the command. A
* notable idiosyncrasy of the Command(s) API to be mindful of here is that
* the command may be issued by the local participant.
*/
_onCustomRoleCommand ({ attributes }, id) {
// We require to know who issued the command because (1) only a
// moderator is allowed to send commands and (2) a command MUST be
// issued by a defined commander.
if (typeof id === 'undefined'
|| this._conference.isLocalId(id)
|| !attributes.recorderRole)
return;
var isRecorder = (attributes.recorderRole == 'true');
if (isRecorder)
VideoLayout.enableDeviceAvailabilityIcons(id, isRecorder);
}
}
export default Recorder;