feat(remotecontrol): UI for requesting permissions
This commit is contained in:
parent
846fb9abb0
commit
a4d5c41b3a
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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":
|
||||||
{
|
{
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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"
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue