feat(remotecontrol): UI for requesting permissions
This commit is contained in:
parent
846fb9abb0
commit
a4d5c41b3a
|
@ -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
|
||||
|
|
|
@ -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 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
.remote-control-spinner {
|
||||
top: 6px;
|
||||
left: 2px;
|
||||
}
|
||||
|
|
|
@ -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":
|
||||
{
|
||||
|
|
|
@ -1441,4 +1441,11 @@ UI.hideUserMediaPermissionsGuidanceOverlay = function () {
|
|||
GumPermissionsOverlay.hide();
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles user's features changes.
|
||||
*/
|
||||
UI.onUserFeaturesChanged = function (user) {
|
||||
VideoLayout.onUserFeaturesChanged(user);
|
||||
};
|
||||
|
||||
module.exports = UI;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<boolean>} - 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<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();
|
||||
|
|
|
@ -26,7 +26,8 @@ export const EVENT_TYPES = {
|
|||
export const PERMISSIONS_ACTIONS = {
|
||||
request: "request",
|
||||
grant: "grant",
|
||||
deny: "deny"
|
||||
deny: "deny",
|
||||
error: "error"
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue