feat(remotecontrol): UI for requesting permissions

This commit is contained in:
hristoterezov 2017-01-05 19:18:07 -06:00
parent 846fb9abb0
commit a4d5c41b3a
11 changed files with 378 additions and 90 deletions

View File

@ -488,11 +488,11 @@ export default {
}).then(([tracks, con]) => { }).then(([tracks, con]) => {
logger.log('initialized with %s local tracks', tracks.length); logger.log('initialized with %s local tracks', tracks.length);
APP.connection = connection = con; APP.connection = connection = con;
this.isDesktopSharingEnabled =
JitsiMeetJS.isDesktopSharingEnabled();
APP.remoteControl.init(); APP.remoteControl.init();
this._bindConnectionFailedHandler(con); this._bindConnectionFailedHandler(con);
this._createRoom(tracks); this._createRoom(tracks);
this.isDesktopSharingEnabled =
JitsiMeetJS.isDesktopSharingEnabled();
if (UIUtil.isButtonEnabled('contacts') if (UIUtil.isButtonEnabled('contacts')
&& !interfaceConfig.filmStripOnly) { && !interfaceConfig.filmStripOnly) {
@ -985,7 +985,7 @@ export default {
let externalInstallation = false; let externalInstallation = false;
if (shareScreen) { if (shareScreen) {
createLocalTracks({ this.screenSharingPromise = createLocalTracks({
devices: ['desktop'], devices: ['desktop'],
desktopSharingExtensionExternalInstallation: { desktopSharingExtensionExternalInstallation: {
interval: 500, interval: 500,
@ -1075,7 +1075,9 @@ export default {
}); });
} else { } else {
APP.remoteControl.receiver.stop(); APP.remoteControl.receiver.stop();
createLocalTracks({ devices: ['video'] }).then( this.screenSharingPromise = createLocalTracks(
{ devices: ['video'] })
.then(
([stream]) => this.useVideoStream(stream) ([stream]) => this.useVideoStream(stream)
).then(() => { ).then(() => {
this.videoSwitchInProgress = false; 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) => { room.on(ConferenceEvents.USER_JOINED, (id, user) => {
if (user.isHidden()) if (user.isHidden())
return; return;
@ -1780,6 +1784,7 @@ export default {
*/ */
hangup (requestFeedback = false) { hangup (requestFeedback = false) {
APP.UI.hideRingOverLay(); APP.UI.hideRingOverLay();
APP.remoteControl.receiver.enable(false);
let requestFeedbackPromise = requestFeedback let requestFeedbackPromise = requestFeedback
? APP.UI.requestFeedbackOnHangup() ? APP.UI.requestFeedbackOnHangup()
// false - because the thank you dialog shouldn't be displayed // false - because the thank you dialog shouldn't be displayed

View File

@ -92,7 +92,7 @@
0 0 3px $videoThumbnailSelected !important; 0 0 3px $videoThumbnailSelected !important;
} }
.remotevideomenu { .remotevideomenu > .icon-menu {
display: none; display: none;
} }
@ -105,7 +105,7 @@
box-shadow: inset 0 0 3px $videoThumbnailHovered, box-shadow: inset 0 0 3px $videoThumbnailHovered,
0 0 3px $videoThumbnailHovered; 0 0 3px $videoThumbnailHovered;
.remotevideomenu { .remotevideomenu > .icon-menu {
display: inline-block; display: inline-block;
} }
} }

View File

@ -6,7 +6,6 @@
padding: 0; padding: 0;
margin: 2px 0; margin: 2px 0;
bottom: 0; bottom: 0;
width: 100px;
height: auto; height: auto;
&:first-child { &:first-child {
@ -67,3 +66,8 @@
span.remotevideomenu:hover ul.popupmenu, ul.popupmenu:hover { span.remotevideomenu:hover ul.popupmenu, ul.popupmenu:hover {
display:block !important; display:block !important;
} }
.remote-control-spinner {
top: 6px;
left: 2px;
}

View File

@ -156,8 +156,8 @@
"kick": "Kick out", "kick": "Kick out",
"muted": "Muted", "muted": "Muted",
"domute": "Mute", "domute": "Mute",
"flip": "Flip" "flip": "Flip",
"remoteControl": "Remote control"
}, },
"connectionindicator": "connectionindicator":
{ {
@ -316,7 +316,12 @@
"externalInstallationMsg": "You need to install our desktop sharing extension.", "externalInstallationMsg": "You need to install our desktop sharing extension.",
"muteParticipantTitle": "Mute this participant?", "muteParticipantTitle": "Mute this participant?",
"muteParticipantBody": "You won't be able to unmute them, but they can unmute themselves at any time.", "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": "email":
{ {

View File

@ -1441,4 +1441,11 @@ UI.hideUserMediaPermissionsGuidanceOverlay = function () {
GumPermissionsOverlay.hide(); GumPermissionsOverlay.hide();
}; };
/**
* Handles user's features changes.
*/
UI.onUserFeaturesChanged = function (user) {
VideoLayout.onUserFeaturesChanged(user);
};
module.exports = UI; module.exports = UI;

View File

@ -29,6 +29,7 @@ function RemoteVideo(user, VideoLayout, emitter) {
this.videoSpanId = `participant_${this.id}`; this.videoSpanId = `participant_${this.id}`;
SmallVideo.call(this, VideoLayout); SmallVideo.call(this, VideoLayout);
this.hasRemoteVideoMenu = false; this.hasRemoteVideoMenu = false;
this._supportsRemoteControl = false;
this.addRemoteVideoContainer(); this.addRemoteVideoContainer();
this.connectionIndicator = new ConnectionIndicator(this, this.id); this.connectionIndicator = new ConnectionIndicator(this, this.id);
this.setDisplayName(); this.setDisplayName();
@ -64,7 +65,7 @@ RemoteVideo.prototype.addRemoteVideoContainer = function() {
this.initBrowserSpecificProperties(); this.initBrowserSpecificProperties();
if (APP.conference.isModerator) { if (APP.conference.isModerator || this._supportsRemoteControl) {
this.addRemoteVideoMenu(); this.addRemoteVideoMenu();
} }
@ -106,14 +107,6 @@ RemoteVideo.prototype._initPopupMenu = function (popupMenuElement) {
// call the original show, passing its actual this // call the original show, passing its actual this
origShowFunc.call(this.popover); origShowFunc.call(this.popover);
}.bind(this); }.bind(this);
// 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,7 +132,9 @@ RemoteVideo.prototype._generatePopupContent = function () {
let popupmenuElement = document.createElement('ul'); let popupmenuElement = document.createElement('ul');
popupmenuElement.className = 'popupmenu'; popupmenuElement.className = 'popupmenu';
popupmenuElement.id = `remote_popupmenu_${this.id}`; popupmenuElement.id = `remote_popupmenu_${this.id}`;
let menuItems = [];
if(APP.conference.isModerator) {
let muteTranslationKey; let muteTranslationKey;
let muteClassName; let muteClassName;
if (this.isAudioMuted) { if (this.isAudioMuted) {
@ -153,7 +148,7 @@ RemoteVideo.prototype._generatePopupContent = function () {
let muteHandler = this._muteHandler.bind(this); let muteHandler = this._muteHandler.bind(this);
let kickHandler = this._kickHandler.bind(this); let kickHandler = this._kickHandler.bind(this);
let menuItems = [ menuItems = [
{ {
id: 'mutelink_' + this.id, id: 'mutelink_' + this.id,
handler: muteHandler, handler: muteHandler,
@ -171,6 +166,34 @@ RemoteVideo.prototype._generatePopupContent = function () {
} }
} }
]; ];
}
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 => { menuItems.forEach(el => {
let menuItem = this._generatePopupMenuItem(el); let menuItem = this._generatePopupMenuItem(el);
@ -182,6 +205,68 @@ RemoteVideo.prototype._generatePopupContent = function () {
return popupmenuElement; 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 () { RemoteVideo.prototype._muteHandler = function () {
if (this.isAudioMuted) if (this.isAudioMuted)
return; return;
@ -244,8 +329,7 @@ RemoteVideo.prototype._generatePopupMenuItem = function (opts = {}) {
linkItem.appendChild(textContent); linkItem.appendChild(textContent);
linkItem.id = id; linkItem.id = id;
// Delegate event to the document. linkItem.onclick = handler;
$(document).on("click", `#${id}`, handler);
menuItem.appendChild(linkItem); menuItem.appendChild(linkItem);
return menuItem; return menuItem;

View File

@ -406,6 +406,7 @@ var VideoLayout = {
remoteVideo = smallVideo; remoteVideo = smallVideo;
else else
remoteVideo = new RemoteVideo(user, VideoLayout, eventEmitter); remoteVideo = new RemoteVideo(user, VideoLayout, eventEmitter);
this._setRemoteControlProperties(user, remoteVideo);
this.addRemoteVideoContainer(id, remoteVideo); this.addRemoteVideoContainer(id, remoteVideo);
}, },
@ -1158,12 +1159,36 @@ var VideoLayout = {
* Sets the flipX state of the local video. * Sets the flipX state of the local video.
* @param {boolean} true for flipped otherwise false; * @param {boolean} true for flipped otherwise false;
*/ */
setLocalFlipX: function (val) { setLocalFlipX (val) {
this.localFlipX = 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; export default VideoLayout;

View File

@ -55,20 +55,34 @@ export default class Controller extends RemoteControlParticipant {
super(); super();
this.controlledParticipant = null; this.controlledParticipant = null;
this.requestedParticipant = 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. * Requests permissions from the remote control receiver side.
* @param {string} userId the user id of the participant that will be * @param {string} userId the user id of the participant that will be
* requested. * requested.
* @returns {Promise<boolean>} - resolve values:
* true - accept
* false - deny
* null - the participant has left.
*/ */
requestPermissions(userId) { requestPermissions(userId) {
if(!this.enabled) { if(!this.enabled) {
return Promise.reject(new Error("Remote control is disabled!")); return Promise.reject(new Error("Remote control is disabled!"));
} }
return new Promise((resolve, reject) => { 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; let result = null;
try { try {
result = this._handleReply(participant, event); result = this._handleReply(participant, event);
@ -76,25 +90,27 @@ export default class Controller extends RemoteControlParticipant {
reject(e); reject(e);
} }
if(result !== null) { if(result !== null) {
this.requestedParticipant = null; clearRequest();
APP.conference.removeConferenceListener(
ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
permissionsReplyListener);
resolve(result); resolve(result);
} }
}; };
const onUserLeft = (id) => {
if(id === this.requestedParticipant) {
clearRequest();
resolve(null);
}
};
APP.conference.addConferenceListener( APP.conference.addConferenceListener(
ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED, ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
permissionsReplyListener); permissionsReplyListener);
APP.conference.addConferenceListener(ConferenceEvents.USER_LEFT,
onUserLeft);
this.requestedParticipant = userId; this.requestedParticipant = userId;
this._sendRemoteControlEvent(userId, { this._sendRemoteControlEvent(userId, {
type: EVENT_TYPES.permissions, type: EVENT_TYPES.permissions,
action: PERMISSIONS_ACTIONS.request action: PERMISSIONS_ACTIONS.request
}, e => { }, e => {
APP.conference.removeConferenceListener( clearRequest();
ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
permissionsReplyListener);
this.requestedParticipant = null;
reject(e); reject(e);
}); });
}); });
@ -112,13 +128,17 @@ export default class Controller extends RemoteControlParticipant {
if(this.enabled && event.type === REMOTE_CONTROL_EVENT_TYPE if(this.enabled && event.type === REMOTE_CONTROL_EVENT_TYPE
&& remoteControlEvent.type === EVENT_TYPES.permissions && remoteControlEvent.type === EVENT_TYPES.permissions
&& userId === this.requestedParticipant) { && userId === this.requestedParticipant) {
if(remoteControlEvent.action === PERMISSIONS_ACTIONS.grant) { switch(remoteControlEvent.action) {
case PERMISSIONS_ACTIONS.grant: {
this.controlledParticipant = userId; this.controlledParticipant = userId;
this._start(); this._start();
return true; return true;
} else if(remoteControlEvent.action === PERMISSIONS_ACTIONS.deny) { }
case PERMISSIONS_ACTIONS.deny:
return false; return false;
} else { case PERMISSIONS_ACTIONS.error:
throw new Error("Error occurred on receiver side");
default:
throw new Error("Unknown reply received!"); throw new Error("Unknown reply received!");
} }
} else { } else {
@ -149,7 +169,9 @@ export default class Controller extends RemoteControlParticipant {
return; return;
APP.conference.addConferenceListener( APP.conference.addConferenceListener(
ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED, ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
this.stopListener); this._stopListener);
APP.conference.addConferenceListener(ConferenceEvents.USER_LEFT,
this._userLeftListener);
this.area = $("#largeVideoWrapper"); this.area = $("#largeVideoWrapper");
this.area.mousemove(event => { this.area.mousemove(event => {
const position = this.area.position(); 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() { _stop() {
if(!this.controlledParticipant) {
return;
}
APP.conference.removeConferenceListener( APP.conference.removeConferenceListener(
ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED, ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
this.stopListener); this._stopListener);
APP.conference.removeConferenceListener(ConferenceEvents.USER_LEFT,
this._userLeftListener);
this.controlledParticipant = null; this.controlledParticipant = null;
this.area.off( "mousemove" ); this.area.off( "mousemove" );
this.area.off( "mousedown" ); this.area.off( "mousedown" );
@ -194,6 +221,23 @@ export default class Controller extends RemoteControlParticipant {
$(window).off( "keydown"); $(window).off( "keydown");
$(window).off( "keyup"); $(window).off( "keyup");
this.area[0].onmousewheel = undefined; 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. * Handler for key press events.
* @param {String} type the type of event ("keydown"/"keyup") * @param {String} type the type of event ("keydown"/"keyup")
@ -220,4 +280,14 @@ export default class Controller extends RemoteControlParticipant {
modifiers: getModifiers(event), 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();
}
}
} }

View File

@ -1,4 +1,4 @@
/* global APP, JitsiMeetJS */ /* global APP, JitsiMeetJS, interfaceConfig */
import {DISCO_REMOTE_CONTROL_FEATURE, REMOTE_CONTROL_EVENT_TYPE, EVENT_TYPES, import {DISCO_REMOTE_CONTROL_FEATURE, REMOTE_CONTROL_EVENT_TYPE, EVENT_TYPES,
PERMISSIONS_ACTIONS} from "../../service/remotecontrol/Constants"; PERMISSIONS_ACTIONS} from "../../service/remotecontrol/Constants";
import RemoteControlParticipant from "./RemoteControlParticipant"; import RemoteControlParticipant from "./RemoteControlParticipant";
@ -21,6 +21,7 @@ export default class Receiver extends RemoteControlParticipant {
this.controller = null; this.controller = null;
this._remoteControlEventsListener this._remoteControlEventsListener
= this._onRemoteControlEvent.bind(this); = 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. * @param {boolean} enabled the new state.
*/ */
enable(enabled) { enable(enabled) {
if(this.enabled !== enabled && enabled === true) { if(this.enabled !== enabled) {
this.enabled = enabled; this.enabled = enabled;
}
if(enabled === true) {
// Announce remote control support. // Announce remote control support.
APP.connection.addFeature(DISCO_REMOTE_CONTROL_FEATURE, true); APP.connection.addFeature(DISCO_REMOTE_CONTROL_FEATURE, true);
APP.conference.addConferenceListener( APP.conference.addConferenceListener(
ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED, ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
this._remoteControlEventsListener); 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 * 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() { stop() {
APP.conference.removeConferenceListener( if(!this.controller) {
ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED, return;
this._remoteControlEventsListener); }
const event = { this._sendRemoteControlEvent(this.controller, {
type: EVENT_TYPES.stop type: EVENT_TYPES.stop
}; });
this._sendRemoteControlEvent(this.controller, event); this._stop();
this.controller = null;
APP.API.sendRemoteControlEvent(event);
} }
/** /**
@ -67,9 +98,15 @@ export default class Receiver extends RemoteControlParticipant {
&& remoteControlEvent.action === PERMISSIONS_ACTIONS.request) { && remoteControlEvent.action === PERMISSIONS_ACTIONS.request) {
remoteControlEvent.userId = participant.getId(); remoteControlEvent.userId = participant.getId();
remoteControlEvent.userJID = participant.getJid(); 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()) { } else if(this.controller !== participant.getId()) {
return; return;
} else if(remoteControlEvent.type === EVENT_TYPES.stop) {
this._stop();
return;
} }
APP.API.sendRemoteControlEvent(remoteControlEvent); APP.API.sendRemoteControlEvent(remoteControlEvent);
} }
@ -83,11 +120,45 @@ export default class Receiver extends RemoteControlParticipant {
*/ */
_onRemoteControlPermissionsEvent(userId, action) { _onRemoteControlPermissionsEvent(userId, action) {
if(action === PERMISSIONS_ACTIONS.grant) { if(action === PERMISSIONS_ACTIONS.grant) {
APP.conference.addConferenceListener(ConferenceEvents.USER_LEFT,
this._userLeftListener);
this.controller = userId; 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, { this._sendRemoteControlEvent(userId, {
type: EVENT_TYPES.permissions, type: EVENT_TYPES.permissions,
action: action 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();
}
}
} }

View File

@ -1,7 +1,7 @@
/* global APP, config */ /* global APP, config */
import Controller from "./Controller"; import Controller from "./Controller";
import Receiver from "./Receiver"; import Receiver from "./Receiver";
import {EVENT_TYPES} import {EVENT_TYPES, DISCO_REMOTE_CONTROL_FEATURE}
from "../../service/remotecontrol/Constants"; from "../../service/remotecontrol/Constants";
/** /**
@ -24,7 +24,8 @@ class RemoteControl {
* enabled or not, initializes the API module. * enabled or not, initializes the API module.
*/ */
init() { init() {
if(config.disableRemoteControl || this.initialized) { if(config.disableRemoteControl || this.initialized
|| !APP.conference.isDesktopSharingEnabled) {
return; return;
} }
this.initialized = true; this.initialized = true;
@ -32,6 +33,9 @@ class RemoteControl {
forceEnable: true, forceEnable: true,
}); });
this.controller.enable(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<boolean>} 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(); export default new RemoteControl();

View File

@ -26,7 +26,8 @@ export const EVENT_TYPES = {
export const PERMISSIONS_ACTIONS = { export const PERMISSIONS_ACTIONS = {
request: "request", request: "request",
grant: "grant", grant: "grant",
deny: "deny" deny: "deny",
error: "error"
}; };
/** /**