diff --git a/conference.js b/conference.js index 447859c5b..618b230a2 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() @@ -507,6 +498,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 +568,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 +580,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 +918,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 +929,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 +939,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 +952,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 +1099,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 +1112,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 +1124,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); diff --git a/modules/UI/Feedback.js b/modules/UI/Feedback.js index 457385112..92f79bc37 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 97cdddf82..d9d46deb4 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/recording/Recording.js b/modules/UI/recording/Recording.js index 38e65504e..23a660b29 100644 --- a/modules/UI/recording/Recording.js +++ b/modules/UI/recording/Recording.js @@ -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'); diff --git a/modules/UI/toolbars/BottomToolbar.js b/modules/UI/toolbars/BottomToolbar.js index eae97444a..4ef781203 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 03e76ba67..7a95487ee 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 ac13c6910..09bff508f 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}); - $('#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(); diff --git a/modules/UI/videolayout/SmallVideo.js b/modules/UI/videolayout/SmallVideo.js index f40d2e9be..a809aaebb 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 772e05f37..7c94da9db 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. diff --git a/modules/recorder/Recorder.js b/modules/recorder/Recorder.js new file mode 100644 index 000000000..9f2f7d870 --- /dev/null +++ b/modules/recorder/Recorder.js @@ -0,0 +1,100 @@ +/* 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 + * @param UI the {UI} which is the source (model/state) to be sent to + * remote participants if the local participant is the moderator or the + * destination (model/state) to receive from the remote moderator if the + * local participant is not the moderator + */ + constructor (conference, UI) { + this._conference = conference; + this._UI = UI; + + // 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;