From e43c5ab54c37bbfcb6dfea5ea4c632007925f208 Mon Sep 17 00:00:00 2001 From: yanas Date: Thu, 7 Apr 2016 12:08:00 -0500 Subject: [PATCH] Add custom-role to presence and special view for Recorders --- conference.js | 114 ++++++++++++++------------ modules/UI/Feedback.js | 32 +++++++- modules/UI/UI.js | 17 +++- modules/UI/toolbars/BottomToolbar.js | 21 ++++- modules/UI/toolbars/Toolbar.js | 76 +++++++++++++++-- modules/UI/toolbars/ToolbarToggler.js | 48 +++++------ modules/UI/videolayout/SmallVideo.js | 18 ++++ modules/UI/videolayout/VideoLayout.js | 19 +++++ 8 files changed, 250 insertions(+), 95 deletions(-) diff --git a/conference.js b/conference.js index c688578999..16df73728a 100644 --- a/conference.js +++ b/conference.js @@ -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() @@ -494,32 +485,6 @@ export default { getLogs () { return room.getLogs(); }, - _createRoom (localTracks) { - room = connection.initJitsiConference(APP.conference.roomName, - this._getConferenceOptions()); - this.localId = room.myUserId(); - localTracks.forEach((track) => { - if (track.isAudioTrack()) { - this.useAudioStream(track); - } else if (track.isVideoTrack()) { - this.useVideoStream(track); - } - }); - roomLocker = createRoomLocker(room); - this._room = room; // FIXME do not use this - - let email = APP.settings.getEmail(); - email && sendEmail(email); - - let nick = APP.settings.getDisplayName(); - if (config.useNicks && !nick) { - nick = APP.UI.askForNickname(); - APP.settings.setDisplayName(nick); - } - nick && room.setDisplayName(nick); - - this._setupListeners(); - }, /** * Exposes a Command(s) API on this instance. It is necessitated by (1) the @@ -531,20 +496,30 @@ export default { * 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 () { + addCommandListener () { room.addCommandListener.apply(room, arguments); }, /** * Removes command. * @param name {String} the name of the command. */ - removeCommand () { + removeCommand () { room.removeCommand.apply(room, arguments); }, /** @@ -552,7 +527,7 @@ export default { * @param name {String} the name of the command. * @param values {Object} with keys and values that will be sent. */ - sendCommand () { + sendCommand () { room.sendCommand.apply(room, arguments); }, /** @@ -560,9 +535,36 @@ export default { * @param name {String} the name of the command. * @param values {Object} with keys and values that will be sent. */ - sendCommandOnce () { + sendCommandOnce () { room.sendCommandOnce.apply(room, arguments); - }, + } + }, + + _createRoom (localTracks) { + room = connection.initJitsiConference(APP.conference.roomName, + this._getConferenceOptions()); + this.localId = room.myUserId(); + localTracks.forEach((track) => { + if (track.isAudioTrack()) { + this.useAudioStream(track); + } else if (track.isVideoTrack()) { + this.useVideoStream(track); + } + }); + roomLocker = createRoomLocker(room); + this._room = room; // FIXME do not use this + + let email = APP.settings.getEmail(); + email && sendEmail(this.commands.defaults.EMAIL, email); + + let nick = APP.settings.getDisplayName(); + if (config.useNicks && !nick) { + nick = APP.UI.askForNickname(); + APP.settings.setDisplayName(nick); + } + nick && room.setDisplayName(nick); + + this._setupListeners(); }, _getConferenceOptions() { @@ -721,6 +723,8 @@ export default { * Setup interaction between conference and UI. */ _setupListeners () { + var self = this; + // add local streams when joined to the conference room.on(ConferenceEvents.CONFERENCE_JOINED, () => { APP.UI.mucJoined(); @@ -903,7 +907,8 @@ export default { APP.UI.updateLocalStats(percent, stats); // send local stats to other users - room.sendCommandOnce(Commands.CONNECTION_QUALITY, { + room.sendCommandOnce(self.commands.defaults.CONNECTION_QUALITY, + { children: ConnectionQuality.convertToMUCStats(stats), attributes: { xmlns: 'http://jitsi.org/jitmeet/stats' @@ -913,8 +918,9 @@ export default { ); // listen to remote stats - room.addCommandListener(Commands.CONNECTION_QUALITY,(values, from) => { - ConnectionQuality.updateRemoteStats(from, values); + room.addCommandListener(self.commands.defaults.CONNECTION_QUALITY, + (values, from) => { + ConnectionQuality.updateRemoteStats(from, values); }); ConnectionQuality.addListener(CQEvents.REMOTESTATS_UPDATED, @@ -922,7 +928,7 @@ export default { APP.UI.updateRemoteStats(id, percent, stats); }); - room.addCommandListener(Commands.ETHERPAD, ({value}) => { + room.addCommandListener(self.commands.defaults.ETHERPAD, ({value}) => { APP.UI.initEtherpad(value); }); @@ -935,9 +941,9 @@ export default { APP.settings.setEmail(email); APP.UI.setUserAvatar(room.myUserId(), email); - sendEmail(email); + sendEmail(self.commands.defaults.EMAIL, email); }); - room.addCommandListener(Commands.EMAIL, (data) => { + room.addCommandListener(self.commands.defaults.EMAIL, (data) => { APP.UI.setUserAvatar(data.attributes.id, data.value); }); @@ -1082,8 +1088,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(self.commands.defaults.SHARED_VIDEO); + room.sendCommandOnce(self.commands.defaults.SHARED_VIDEO, { value: url, attributes: { state: state, @@ -1095,8 +1101,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(self.commands.defaults.SHARED_VIDEO); + room.sendCommand(self.commands.defaults.SHARED_VIDEO, { value: url, attributes: { state: state, @@ -1107,7 +1113,7 @@ export default { } }); room.addCommandListener( - Commands.SHARED_VIDEO, ({value, attributes}, id) => { + self.commands.defaults.SHARED_VIDEO, ({value, attributes}, id) => { if (attributes.state === 'stop') { APP.UI.stopSharedVideo(id, attributes); diff --git a/modules/UI/Feedback.js b/modules/UI/Feedback.js index 457385112f..92f79bc375 100644 --- a/modules/UI/Feedback.js +++ b/modules/UI/Feedback.js @@ -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. * @@ -85,11 +100,15 @@ var Feedback = { * @param emitter the EventEmitter to associate with the Feedback. */ init: function (emitter) { + // Initialise to enabled. + this.enabled = true; + // CallStats is the way we send feedback, so we don't have to initialise // if callstats isn't enabled. if (!APP.conference.isCallstatsEnabled()) return; - $("#feedbackButtonDiv").css("display", "block"); + + _showFeedbackButton(true); $("#feedbackButton").click(function (event) { Feedback.openFeedbackWindow(); }); @@ -100,13 +119,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. diff --git a/modules/UI/UI.js b/modules/UI/UI.js index 97cdddf82b..d9d46deb4c 100644 --- a/modules/UI/UI.js +++ b/modules/UI/UI.js @@ -29,6 +29,8 @@ 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,7 +44,8 @@ let followMeHandler; * Prompt user for nickname. */ function promptDisplayName() { - let nickRequiredMsg = APP.translation.translateString("dialog.displayNameRequired"); + let nickRequiredMsg + = APP.translation.translateString("dialog.displayNameRequired"); let defaultNickMsg = APP.translation.translateString( "defaultNickname", {name: "Jane Pink"} ); @@ -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, UI); + // Once we've joined the muc show the toolbar ToolbarToggler.showToolbar(); diff --git a/modules/UI/toolbars/BottomToolbar.js b/modules/UI/toolbars/BottomToolbar.js index eae97444a4..4ef7812032 100644 --- a/modules/UI/toolbars/BottomToolbar.js +++ b/modules/UI/toolbars/BottomToolbar.js @@ -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); diff --git a/modules/UI/toolbars/Toolbar.js b/modules/UI/toolbars/Toolbar.js index 03e76ba677..7a95487ee5 100644 --- a/modules/UI/toolbars/Toolbar.js +++ b/modules/UI/toolbars/Toolbar.js @@ -171,6 +171,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 +181,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 +264,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 +353,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 true if currently visible, false - 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}); } }; diff --git a/modules/UI/toolbars/ToolbarToggler.js b/modules/UI/toolbars/ToolbarToggler.js index ac13c69109..09bff508f1 100644 --- a/modules/UI/toolbars/ToolbarToggler.js +++ b/modules/UI/toolbars/ToolbarToggler.js @@ -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}); + + var updateTimeout = false; + if (Toolbar.isEnabled() && !Toolbar.isVisible()) { + Toolbar.show(); $('#subject').animate({top: "+=40"}, 300); - if (!BottomToolbar.isVisible()) { - BottomToolbar.show(true); - } + 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(); diff --git a/modules/UI/videolayout/SmallVideo.js b/modules/UI/videolayout/SmallVideo.js index f40d2e9be2..a809aaebbe 100644 --- a/modules/UI/videolayout/SmallVideo.js +++ b/modules/UI/videolayout/SmallVideo.js @@ -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; diff --git a/modules/UI/videolayout/VideoLayout.js b/modules/UI/videolayout/VideoLayout.js index 772e05f37d..7c94da9db0 100644 --- a/modules/UI/videolayout/VideoLayout.js +++ b/modules/UI/videolayout/VideoLayout.js @@ -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.