From a4d5c41b3ab103207b18d84b350e38d3023596b5 Mon Sep 17 00:00:00 2001 From: hristoterezov Date: Thu, 5 Jan 2017 19:18:07 -0600 Subject: [PATCH] feat(remotecontrol): UI for requesting permissions --- conference.js | 13 +- css/_filmstrip.scss | 6 +- css/_popup_menu.scss | 8 +- lang/main.json | 11 +- modules/UI/UI.js | 7 ++ modules/UI/videolayout/RemoteVideo.js | 162 +++++++++++++++++++------ modules/UI/videolayout/VideoLayout.js | 31 ++++- modules/remotecontrol/Controller.js | 112 +++++++++++++---- modules/remotecontrol/Receiver.js | 95 +++++++++++++-- modules/remotecontrol/RemoteControl.js | 20 ++- service/remotecontrol/Constants.js | 3 +- 11 files changed, 378 insertions(+), 90 deletions(-) diff --git a/conference.js b/conference.js index 36309b9cd..d198b354e 100644 --- a/conference.js +++ b/conference.js @@ -488,11 +488,11 @@ export default { }).then(([tracks, con]) => { logger.log('initialized with %s local tracks', tracks.length); APP.connection = connection = con; + this.isDesktopSharingEnabled = + JitsiMeetJS.isDesktopSharingEnabled(); APP.remoteControl.init(); this._bindConnectionFailedHandler(con); this._createRoom(tracks); - this.isDesktopSharingEnabled = - JitsiMeetJS.isDesktopSharingEnabled(); if (UIUtil.isButtonEnabled('contacts') && !interfaceConfig.filmStripOnly) { @@ -985,7 +985,7 @@ export default { let externalInstallation = false; if (shareScreen) { - createLocalTracks({ + this.screenSharingPromise = createLocalTracks({ devices: ['desktop'], desktopSharingExtensionExternalInstallation: { interval: 500, @@ -1075,7 +1075,9 @@ export default { }); } else { APP.remoteControl.receiver.stop(); - createLocalTracks({ devices: ['video'] }).then( + this.screenSharingPromise = createLocalTracks( + { devices: ['video'] }) + .then( ([stream]) => this.useVideoStream(stream) ).then(() => { this.videoSwitchInProgress = false; @@ -1107,6 +1109,8 @@ export default { } ); + room.on(ConferenceEvents.PARTCIPANT_FEATURES_CHANGED, + user => APP.UI.onUserFeaturesChanged(user)); room.on(ConferenceEvents.USER_JOINED, (id, user) => { if (user.isHidden()) return; @@ -1780,6 +1784,7 @@ export default { */ hangup (requestFeedback = false) { APP.UI.hideRingOverLay(); + APP.remoteControl.receiver.enable(false); let requestFeedbackPromise = requestFeedback ? APP.UI.requestFeedbackOnHangup() // false - because the thank you dialog shouldn't be displayed diff --git a/css/_filmstrip.scss b/css/_filmstrip.scss index dddbc886e..1b6dd089e 100644 --- a/css/_filmstrip.scss +++ b/css/_filmstrip.scss @@ -92,7 +92,7 @@ 0 0 3px $videoThumbnailSelected !important; } - .remotevideomenu { + .remotevideomenu > .icon-menu { display: none; } @@ -105,7 +105,7 @@ box-shadow: inset 0 0 3px $videoThumbnailHovered, 0 0 3px $videoThumbnailHovered; - .remotevideomenu { + .remotevideomenu > .icon-menu { display: inline-block; } } @@ -121,4 +121,4 @@ } } } -} \ No newline at end of file +} diff --git a/css/_popup_menu.scss b/css/_popup_menu.scss index f129321e0..2283926d4 100644 --- a/css/_popup_menu.scss +++ b/css/_popup_menu.scss @@ -6,7 +6,6 @@ padding: 0; margin: 2px 0; bottom: 0; - width: 100px; height: auto; &:first-child { @@ -66,4 +65,9 @@ span.remotevideomenu:hover ul.popupmenu, ul.popupmenu:hover { display:block !important; -} \ No newline at end of file +} + +.remote-control-spinner { + top: 6px; + left: 2px; +} diff --git a/lang/main.json b/lang/main.json index 2dc1487fd..3e398633a 100644 --- a/lang/main.json +++ b/lang/main.json @@ -156,8 +156,8 @@ "kick": "Kick out", "muted": "Muted", "domute": "Mute", - "flip": "Flip" - + "flip": "Flip", + "remoteControl": "Remote control" }, "connectionindicator": { @@ -316,7 +316,12 @@ "externalInstallationMsg": "You need to install our desktop sharing extension.", "muteParticipantTitle": "Mute this participant?", "muteParticipantBody": "You won't be able to unmute them, but they can unmute themselves at any time.", - "muteParticipantButton": "Mute" + "muteParticipantButton": "Mute", + "remoteControlTitle": "Remote Control", + "remoteControlDeniedMessage": "__user__ rejected your remote control request!", + "remoteControlAllowedMessage": "__user__ accepted your remote control request!", + "remoteControlErrorMessage": "An error occurred while trying to request remote control permissions from __user__!", + "remoteControlStopMessage": "The remote control session ended!" }, "email": { diff --git a/modules/UI/UI.js b/modules/UI/UI.js index 75752268f..624b5c1ba 100644 --- a/modules/UI/UI.js +++ b/modules/UI/UI.js @@ -1441,4 +1441,11 @@ UI.hideUserMediaPermissionsGuidanceOverlay = function () { GumPermissionsOverlay.hide(); }; +/** + * Handles user's features changes. + */ +UI.onUserFeaturesChanged = function (user) { + VideoLayout.onUserFeaturesChanged(user); +}; + module.exports = UI; diff --git a/modules/UI/videolayout/RemoteVideo.js b/modules/UI/videolayout/RemoteVideo.js index ac156ddbd..aae794885 100644 --- a/modules/UI/videolayout/RemoteVideo.js +++ b/modules/UI/videolayout/RemoteVideo.js @@ -29,6 +29,7 @@ function RemoteVideo(user, VideoLayout, emitter) { this.videoSpanId = `participant_${this.id}`; SmallVideo.call(this, VideoLayout); this.hasRemoteVideoMenu = false; + this._supportsRemoteControl = false; this.addRemoteVideoContainer(); this.connectionIndicator = new ConnectionIndicator(this, this.id); this.setDisplayName(); @@ -64,7 +65,7 @@ RemoteVideo.prototype.addRemoteVideoContainer = function() { this.initBrowserSpecificProperties(); - if (APP.conference.isModerator) { + if (APP.conference.isModerator || this._supportsRemoteControl) { this.addRemoteVideoMenu(); } @@ -106,14 +107,6 @@ RemoteVideo.prototype._initPopupMenu = function (popupMenuElement) { // call the original show, passing its actual this origShowFunc.call(this.popover); }.bind(this); - - // override popover hide method so we can cleanup click handlers - let origHideFunc = this.popover.forceHide; - this.popover.forceHide = function () { - $(document).off("click", '#mutelink_' + this.id); - $(document).off("click", '#ejectlink_' + this.id); - origHideFunc.call(this.popover); - }.bind(this); }; /** @@ -139,38 +132,68 @@ RemoteVideo.prototype._generatePopupContent = function () { let popupmenuElement = document.createElement('ul'); popupmenuElement.className = 'popupmenu'; popupmenuElement.id = `remote_popupmenu_${this.id}`; + let menuItems = []; - let muteTranslationKey; - let muteClassName; - if (this.isAudioMuted) { - muteTranslationKey = 'videothumbnail.muted'; - muteClassName = 'mutelink disabled'; - } else { - muteTranslationKey = 'videothumbnail.domute'; - muteClassName = 'mutelink'; + if(APP.conference.isModerator) { + let muteTranslationKey; + let muteClassName; + if (this.isAudioMuted) { + muteTranslationKey = 'videothumbnail.muted'; + muteClassName = 'mutelink disabled'; + } else { + muteTranslationKey = 'videothumbnail.domute'; + muteClassName = 'mutelink'; + } + + let muteHandler = this._muteHandler.bind(this); + let kickHandler = this._kickHandler.bind(this); + + menuItems = [ + { + id: 'mutelink_' + this.id, + handler: muteHandler, + icon: 'icon-mic-disabled', + className: muteClassName, + data: { + i18n: muteTranslationKey + } + }, { + id: 'ejectlink_' + this.id, + handler: kickHandler, + icon: 'icon-kick', + data: { + i18n: 'videothumbnail.kick' + } + } + ]; } - let muteHandler = this._muteHandler.bind(this); - let kickHandler = this._kickHandler.bind(this); - - let menuItems = [ - { - id: 'mutelink_' + this.id, - handler: muteHandler, - icon: 'icon-mic-disabled', - className: muteClassName, - data: { - i18n: muteTranslationKey - } - }, { - id: 'ejectlink_' + this.id, - handler: kickHandler, - icon: 'icon-kick', - data: { - i18n: 'videothumbnail.kick' - } + if(this._supportsRemoteControl) { + let icon, handler, className; + if(APP.remoteControl.controller.getRequestedParticipant() + === this.id) { + handler = () => {}; + className = "requestRemoteControlLink disabled"; + icon = "remote-control-spinner fa fa-spinner fa-spin"; + } else if(!APP.remoteControl.controller.isStarted()) { + handler = this._requestRemoteControlPermissions.bind(this); + icon = "fa fa-play"; + className = "requestRemoteControlLink"; + } else { + handler = this._stopRemoteControl.bind(this); + icon = "fa fa-stop"; + className = "requestRemoteControlLink"; } - ]; + menuItems.push({ + id: 'remoteControl_' + this.id, + handler, + icon, + className, + data: { + i18n: 'videothumbnail.remoteControl' + } + }); + } menuItems.forEach(el => { let menuItem = this._generatePopupMenuItem(el); @@ -182,6 +205,68 @@ RemoteVideo.prototype._generatePopupContent = function () { return popupmenuElement; }; +/** + * Sets the remote control supported value and initializes or updates the menu + * depending on the remote control is supported or not. + * @param {boolean} isSupported + */ +RemoteVideo.prototype.setRemoteControlSupport = function(isSupported = false) { + if(this._supportsRemoteControl === isSupported) { + return; + } + this._supportsRemoteControl = isSupported; + if(!isSupported) { + return; + } + + if(!this.hasRemoteVideoMenu) { + //create menu + this.addRemoteVideoMenu(); + } else { + //update the content + this.updateRemoteVideoMenu(this.isAudioMuted, true); + } + +}; + +/** + * Requests permissions for remote control session. + */ +RemoteVideo.prototype._requestRemoteControlPermissions = function () { + APP.remoteControl.controller.requestPermissions(this.id).then(result => { + if(result === null) { + return; + } + this.updateRemoteVideoMenu(this.isAudioMuted, true); + APP.UI.messageHandler.openMessageDialog( + "dialog.remoteControlTitle", + (result === false) ? "dialog.remoteControlDeniedMessage" + : "dialog.remoteControlAllowedMessage", + {user: this.user.getDisplayName() + || interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME} + ); + }, error => { + logger.error(error); + this.updateRemoteVideoMenu(this.isAudioMuted, true); + APP.UI.messageHandler.openMessageDialog( + "dialog.remoteControlTitle", + "dialog.remoteControlErrorMessage", + {user: this.user.getDisplayName() + || interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME} + ); + }); + this.updateRemoteVideoMenu(this.isAudioMuted, true); +}; + +/** + * Stops remote control session. + */ +RemoteVideo.prototype._stopRemoteControl = function () { + // send message about stopping + APP.remoteControl.controller.stop(); + this.updateRemoteVideoMenu(this.isAudioMuted, true); +}; + RemoteVideo.prototype._muteHandler = function () { if (this.isAudioMuted) return; @@ -244,8 +329,7 @@ RemoteVideo.prototype._generatePopupMenuItem = function (opts = {}) { linkItem.appendChild(textContent); linkItem.id = id; - // Delegate event to the document. - $(document).on("click", `#${id}`, handler); + linkItem.onclick = handler; menuItem.appendChild(linkItem); return menuItem; diff --git a/modules/UI/videolayout/VideoLayout.js b/modules/UI/videolayout/VideoLayout.js index 9f80bc017..97c273f67 100644 --- a/modules/UI/videolayout/VideoLayout.js +++ b/modules/UI/videolayout/VideoLayout.js @@ -406,6 +406,7 @@ var VideoLayout = { remoteVideo = smallVideo; else remoteVideo = new RemoteVideo(user, VideoLayout, eventEmitter); + this._setRemoteControlProperties(user, remoteVideo); this.addRemoteVideoContainer(id, remoteVideo); }, @@ -1158,12 +1159,36 @@ var VideoLayout = { * Sets the flipX state of the local video. * @param {boolean} true for flipped otherwise false; */ - setLocalFlipX: function (val) { + setLocalFlipX (val) { this.localFlipX = val; - }, - getEventEmitter: () => {return eventEmitter;} + getEventEmitter() {return eventEmitter;}, + + /** + * Handles user's features changes. + */ + onUserFeaturesChanged (user) { + let video = this.getSmallVideo(user.getId()); + + if (!video) { + return; + } + this._setRemoteControlProperties(user, video); + }, + + /** + * Sets the remote control properties (checks whether remote control + * is supported and executes remoteVideo.setRemoteControlSupport). + * @param {JitsiParticipant} user the user that will be checked for remote + * control support. + * @param {RemoteVideo} remoteVideo the remoteVideo on which the properties + * will be set. + */ + _setRemoteControlProperties (user, remoteVideo) { + APP.remoteControl.checkUserRemoteControlSupport(user).then(result => + remoteVideo.setRemoteControlSupport(result)); + } }; export default VideoLayout; diff --git a/modules/remotecontrol/Controller.js b/modules/remotecontrol/Controller.js index 6e60e1189..e10356a01 100644 --- a/modules/remotecontrol/Controller.js +++ b/modules/remotecontrol/Controller.js @@ -55,20 +55,34 @@ export default class Controller extends RemoteControlParticipant { super(); this.controlledParticipant = null; this.requestedParticipant = null; - this.stopListener = this._handleRemoteControlStoppedEvent.bind(this); + this._stopListener = this._handleRemoteControlStoppedEvent.bind(this); + this._userLeftListener = this._onUserLeft.bind(this); } /** * Requests permissions from the remote control receiver side. * @param {string} userId the user id of the participant that will be * requested. + * @returns {Promise} - resolve values: + * true - accept + * false - deny + * null - the participant has left. */ requestPermissions(userId) { if(!this.enabled) { return Promise.reject(new Error("Remote control is disabled!")); } return new Promise((resolve, reject) => { - let permissionsReplyListener = (participant, event) => { + const clearRequest = () => { + this.requestedParticipant = null; + APP.conference.removeConferenceListener( + ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED, + permissionsReplyListener); + APP.conference.removeConferenceListener( + ConferenceEvents.USER_LEFT, + onUserLeft); + }; + const permissionsReplyListener = (participant, event) => { let result = null; try { result = this._handleReply(participant, event); @@ -76,25 +90,27 @@ export default class Controller extends RemoteControlParticipant { reject(e); } if(result !== null) { - this.requestedParticipant = null; - APP.conference.removeConferenceListener( - ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED, - permissionsReplyListener); + clearRequest(); resolve(result); } }; + const onUserLeft = (id) => { + if(id === this.requestedParticipant) { + clearRequest(); + resolve(null); + } + }; APP.conference.addConferenceListener( ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED, permissionsReplyListener); + APP.conference.addConferenceListener(ConferenceEvents.USER_LEFT, + onUserLeft); this.requestedParticipant = userId; this._sendRemoteControlEvent(userId, { type: EVENT_TYPES.permissions, action: PERMISSIONS_ACTIONS.request }, e => { - APP.conference.removeConferenceListener( - ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED, - permissionsReplyListener); - this.requestedParticipant = null; + clearRequest(); reject(e); }); }); @@ -112,14 +128,18 @@ export default class Controller extends RemoteControlParticipant { if(this.enabled && event.type === REMOTE_CONTROL_EVENT_TYPE && remoteControlEvent.type === EVENT_TYPES.permissions && userId === this.requestedParticipant) { - if(remoteControlEvent.action === PERMISSIONS_ACTIONS.grant) { - this.controlledParticipant = userId; - this._start(); - return true; - } else if(remoteControlEvent.action === PERMISSIONS_ACTIONS.deny) { - return false; - } else { - throw new Error("Unknown reply received!"); + switch(remoteControlEvent.action) { + case PERMISSIONS_ACTIONS.grant: { + this.controlledParticipant = userId; + this._start(); + return true; + } + case PERMISSIONS_ACTIONS.deny: + return false; + case PERMISSIONS_ACTIONS.error: + throw new Error("Error occurred on receiver side"); + default: + throw new Error("Unknown reply received!"); } } else { //different message type or another user -> ignoring the message @@ -149,7 +169,9 @@ export default class Controller extends RemoteControlParticipant { return; APP.conference.addConferenceListener( ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED, - this.stopListener); + this._stopListener); + APP.conference.addConferenceListener(ConferenceEvents.USER_LEFT, + this._userLeftListener); this.area = $("#largeVideoWrapper"); this.area.mousemove(event => { const position = this.area.position(); @@ -179,12 +201,17 @@ export default class Controller extends RemoteControlParticipant { } /** - * Stops processing the mouse and keyboard events. + * Stops processing the mouse and keyboard events. Removes added listeners. */ _stop() { + if(!this.controlledParticipant) { + return; + } APP.conference.removeConferenceListener( ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED, - this.stopListener); + this._stopListener); + APP.conference.removeConferenceListener(ConferenceEvents.USER_LEFT, + this._userLeftListener); this.controlledParticipant = null; this.area.off( "mousemove" ); this.area.off( "mousedown" ); @@ -194,6 +221,23 @@ export default class Controller extends RemoteControlParticipant { $(window).off( "keydown"); $(window).off( "keyup"); this.area[0].onmousewheel = undefined; + APP.UI.messageHandler.openMessageDialog( + "dialog.remoteControlTitle", + "dialog.remoteControlStopMessage" + ); + } + + /** + * Calls this._stop() and sends stop message to the controlled participant. + */ + stop() { + if(!this.controlledParticipant) { + return; + } + this._sendRemoteControlEvent(this.controlledParticipant, { + type: EVENT_TYPES.stop + }); + this._stop(); } /** @@ -208,6 +252,22 @@ export default class Controller extends RemoteControlParticipant { }); } + /** + * Returns true if the remote control session is started. + * @returns {boolean} + */ + isStarted() { + return this.controlledParticipant !== null; + } + + /** + * Returns the id of the requested participant + * @returns {string} this.requestedParticipant + */ + getRequestedParticipant() { + return this.requestedParticipant; + } + /** * Handler for key press events. * @param {String} type the type of event ("keydown"/"keyup") @@ -220,4 +280,14 @@ export default class Controller extends RemoteControlParticipant { modifiers: getModifiers(event), }); } + + /** + * Calls the stop method if the other side have left. + * @param {string} id - the user id for the participant that have left + */ + _onUserLeft(id) { + if(this.controlledParticipant === id) { + this._stop(); + } + } } diff --git a/modules/remotecontrol/Receiver.js b/modules/remotecontrol/Receiver.js index e23017b7e..eeb1f1763 100644 --- a/modules/remotecontrol/Receiver.js +++ b/modules/remotecontrol/Receiver.js @@ -1,4 +1,4 @@ -/* global APP, JitsiMeetJS */ +/* global APP, JitsiMeetJS, interfaceConfig */ import {DISCO_REMOTE_CONTROL_FEATURE, REMOTE_CONTROL_EVENT_TYPE, EVENT_TYPES, PERMISSIONS_ACTIONS} from "../../service/remotecontrol/Constants"; import RemoteControlParticipant from "./RemoteControlParticipant"; @@ -21,6 +21,7 @@ export default class Receiver extends RemoteControlParticipant { this.controller = null; this._remoteControlEventsListener = this._onRemoteControlEvent.bind(this); + this._userLeftListener = this._onUserLeft.bind(this); } /** @@ -28,30 +29,60 @@ export default class Receiver extends RemoteControlParticipant { * @param {boolean} enabled the new state. */ enable(enabled) { - if(this.enabled !== enabled && enabled === true) { + if(this.enabled !== enabled) { this.enabled = enabled; + } + if(enabled === true) { // Announce remote control support. APP.connection.addFeature(DISCO_REMOTE_CONTROL_FEATURE, true); APP.conference.addConferenceListener( ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED, this._remoteControlEventsListener); + } else { + this._stop(true); + APP.connection.removeFeature(DISCO_REMOTE_CONTROL_FEATURE); + APP.conference.removeConferenceListener( + ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED, + this._remoteControlEventsListener); } } /** * Removes the listener for ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED - * events. + * events. Sends stop message to the wrapper application. Optionally + * displays dialog for informing the user that remote control session + * ended. + * @param {boolean} dontShowDialog - if true the dialog won't be displayed. + */ + _stop(dontShowDialog = false) { + if(!this.controller) { + return; + } + this.controller = null; + APP.conference.removeConferenceListener(ConferenceEvents.USER_LEFT, + this._userLeftListener); + APP.API.sendRemoteControlEvent({ + type: EVENT_TYPES.stop + }); + if(!dontShowDialog) { + APP.UI.messageHandler.openMessageDialog( + "dialog.remoteControlTitle", + "dialog.remoteControlStopMessage" + ); + } + } + + /** + * Calls this._stop() and sends stop message to the controller participant */ stop() { - APP.conference.removeConferenceListener( - ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED, - this._remoteControlEventsListener); - const event = { + if(!this.controller) { + return; + } + this._sendRemoteControlEvent(this.controller, { type: EVENT_TYPES.stop - }; - this._sendRemoteControlEvent(this.controller, event); - this.controller = null; - APP.API.sendRemoteControlEvent(event); + }); + this._stop(); } /** @@ -67,9 +98,15 @@ export default class Receiver extends RemoteControlParticipant { && remoteControlEvent.action === PERMISSIONS_ACTIONS.request) { remoteControlEvent.userId = participant.getId(); remoteControlEvent.userJID = participant.getJid(); - remoteControlEvent.displayName = participant.getDisplayName(); + remoteControlEvent.displayName = participant.getDisplayName() + || interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME; + remoteControlEvent.screenSharing + = APP.conference.isSharingScreen; } else if(this.controller !== participant.getId()) { return; + } else if(remoteControlEvent.type === EVENT_TYPES.stop) { + this._stop(); + return; } APP.API.sendRemoteControlEvent(remoteControlEvent); } @@ -83,11 +120,45 @@ export default class Receiver extends RemoteControlParticipant { */ _onRemoteControlPermissionsEvent(userId, action) { if(action === PERMISSIONS_ACTIONS.grant) { + APP.conference.addConferenceListener(ConferenceEvents.USER_LEFT, + this._userLeftListener); this.controller = userId; + if(!APP.conference.isSharingScreen) { + APP.conference.toggleScreenSharing(); + APP.conference.screenSharingPromise.then(() => { + if(APP.conference.isSharingScreen) { + this._sendRemoteControlEvent(userId, { + type: EVENT_TYPES.permissions, + action: action + }); + } else { + this._sendRemoteControlEvent(userId, { + type: EVENT_TYPES.permissions, + action: PERMISSIONS_ACTIONS.error + }); + } + }).catch(() => { + this._sendRemoteControlEvent(userId, { + type: EVENT_TYPES.permissions, + action: PERMISSIONS_ACTIONS.error + }); + }); + return; + } } this._sendRemoteControlEvent(userId, { type: EVENT_TYPES.permissions, action: action }); } + + /** + * Calls the stop method if the other side have left. + * @param {string} id - the user id for the participant that have left + */ + _onUserLeft(id) { + if(this.controller === id) { + this._stop(); + } + } } diff --git a/modules/remotecontrol/RemoteControl.js b/modules/remotecontrol/RemoteControl.js index 76d06a8ec..e00f8f1af 100644 --- a/modules/remotecontrol/RemoteControl.js +++ b/modules/remotecontrol/RemoteControl.js @@ -1,7 +1,7 @@ /* global APP, config */ import Controller from "./Controller"; import Receiver from "./Receiver"; -import {EVENT_TYPES} +import {EVENT_TYPES, DISCO_REMOTE_CONTROL_FEATURE} from "../../service/remotecontrol/Constants"; /** @@ -24,7 +24,8 @@ class RemoteControl { * enabled or not, initializes the API module. */ init() { - if(config.disableRemoteControl || this.initialized) { + if(config.disableRemoteControl || this.initialized + || !APP.conference.isDesktopSharingEnabled) { return; } this.initialized = true; @@ -32,6 +33,9 @@ class RemoteControl { forceEnable: true, }); this.controller.enable(true); + if(this.enabled) { // supported message came before init. + this._onRemoteControlSupported(); + } } /** @@ -62,6 +66,18 @@ class RemoteControl { } } } + + /** + * Checks whether the passed user supports remote control or not + * @param {JitsiParticipant} user the user to be tested + * @returns {Promise} the promise will be resolved with true if + * the user supports remote control and with false if not. + */ + checkUserRemoteControlSupport(user) { + return user.getFeatures().then(features => + features.has(DISCO_REMOTE_CONTROL_FEATURE), () => false + ); + } } export default new RemoteControl(); diff --git a/service/remotecontrol/Constants.js b/service/remotecontrol/Constants.js index 810e57f11..afc8cc15e 100644 --- a/service/remotecontrol/Constants.js +++ b/service/remotecontrol/Constants.js @@ -26,7 +26,8 @@ export const EVENT_TYPES = { export const PERMISSIONS_ACTIONS = { request: "request", grant: "grant", - deny: "deny" + deny: "deny", + error: "error" }; /**