feat(remotecontrol): Implement requesting remote control permissions
This commit is contained in:
parent
0f33e59e4d
commit
846fb9abb0
|
@ -985,8 +985,6 @@ export default {
|
||||||
let externalInstallation = false;
|
let externalInstallation = false;
|
||||||
|
|
||||||
if (shareScreen) {
|
if (shareScreen) {
|
||||||
// For remote control testing:
|
|
||||||
// remoteControlReceiver.start();
|
|
||||||
createLocalTracks({
|
createLocalTracks({
|
||||||
devices: ['desktop'],
|
devices: ['desktop'],
|
||||||
desktopSharingExtensionExternalInstallation: {
|
desktopSharingExtensionExternalInstallation: {
|
||||||
|
@ -1076,8 +1074,7 @@ export default {
|
||||||
dialogTitleKey, dialogTxt, false);
|
dialogTitleKey, dialogTxt, false);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// For remote control testing:
|
APP.remoteControl.receiver.stop();
|
||||||
// remoteControlReceiver.stop();
|
|
||||||
createLocalTracks({ devices: ['video'] }).then(
|
createLocalTracks({ devices: ['video'] }).then(
|
||||||
([stream]) => this.useVideoStream(stream)
|
([stream]) => this.useVideoStream(stream)
|
||||||
).then(() => {
|
).then(() => {
|
||||||
|
|
|
@ -56,8 +56,8 @@ function initCommands() {
|
||||||
"video-hangup": () => APP.conference.hangup(),
|
"video-hangup": () => APP.conference.hangup(),
|
||||||
"email": APP.conference.changeLocalEmail,
|
"email": APP.conference.changeLocalEmail,
|
||||||
"avatar-url": APP.conference.changeLocalAvatarUrl,
|
"avatar-url": APP.conference.changeLocalAvatarUrl,
|
||||||
"remote-control-supported": isSupported =>
|
"remote-control-event": event =>
|
||||||
APP.remoteControl.onRemoteControlSupported(isSupported)
|
APP.remoteControl.onRemoteControlAPIEvent(event)
|
||||||
};
|
};
|
||||||
Object.keys(commands).forEach(function (key) {
|
Object.keys(commands).forEach(function (key) {
|
||||||
postis.listen(key, args => commands[key](...args));
|
postis.listen(key, args => commands[key](...args));
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
/* global $, APP */
|
/* global $, JitsiMeetJS, APP */
|
||||||
import * as KeyCodes from "../keycode/keycode";
|
import * as KeyCodes from "../keycode/keycode";
|
||||||
import {EVENT_TYPES, API_EVENT_TYPE}
|
import {EVENT_TYPES, REMOTE_CONTROL_EVENT_TYPE, PERMISSIONS_ACTIONS}
|
||||||
from "../../service/remotecontrol/Constants";
|
from "../../service/remotecontrol/Constants";
|
||||||
|
import RemoteControlParticipant from "./RemoteControlParticipant";
|
||||||
|
|
||||||
|
const ConferenceEvents = JitsiMeetJS.events.conference;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract the keyboard key from the keyboard event.
|
* Extract the keyboard key from the keyboard event.
|
||||||
|
@ -44,34 +47,113 @@ function getModifiers(event) {
|
||||||
* It listens for mouse and keyboard events and sends them to the receiver
|
* It listens for mouse and keyboard events and sends them to the receiver
|
||||||
* party of the remote control session.
|
* party of the remote control session.
|
||||||
*/
|
*/
|
||||||
export default class Controller {
|
export default class Controller extends RemoteControlParticipant {
|
||||||
/**
|
/**
|
||||||
* Creates new instance.
|
* Creates new instance.
|
||||||
*/
|
*/
|
||||||
constructor() {
|
constructor() {
|
||||||
this.enabled = false;
|
super();
|
||||||
|
this.controlledParticipant = null;
|
||||||
|
this.requestedParticipant = null;
|
||||||
|
this.stopListener = this._handleRemoteControlStoppedEvent.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enables / Disables the remote control
|
* Requests permissions from the remote control receiver side.
|
||||||
* @param {boolean} enabled the new state.
|
* @param {string} userId the user id of the participant that will be
|
||||||
|
* requested.
|
||||||
*/
|
*/
|
||||||
enable(enabled) {
|
requestPermissions(userId) {
|
||||||
this.enabled = enabled;
|
if(!this.enabled) {
|
||||||
|
return Promise.reject(new Error("Remote control is disabled!"));
|
||||||
|
}
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let permissionsReplyListener = (participant, event) => {
|
||||||
|
let result = null;
|
||||||
|
try {
|
||||||
|
result = this._handleReply(participant, event);
|
||||||
|
} catch (e) {
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
|
if(result !== null) {
|
||||||
|
this.requestedParticipant = null;
|
||||||
|
APP.conference.removeConferenceListener(
|
||||||
|
ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
|
||||||
|
permissionsReplyListener);
|
||||||
|
resolve(result);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
APP.conference.addConferenceListener(
|
||||||
|
ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
|
||||||
|
permissionsReplyListener);
|
||||||
|
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;
|
||||||
|
reject(e);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the reply of the permissions request.
|
||||||
|
* @param {JitsiParticipant} participant the participant that has sent the
|
||||||
|
* reply
|
||||||
|
* @param {object} event the remote control event.
|
||||||
|
*/
|
||||||
|
_handleReply(participant, event) {
|
||||||
|
const remoteControlEvent = event.event;
|
||||||
|
const userId = participant.getId();
|
||||||
|
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!");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
//different message type or another user -> ignoring the message
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles remote control stopped.
|
||||||
|
* @param {JitsiParticipant} participant the participant that has sent the
|
||||||
|
* event
|
||||||
|
* @param {object} event the the remote control event.
|
||||||
|
*/
|
||||||
|
_handleRemoteControlStoppedEvent(participant, event) {
|
||||||
|
if(this.enabled && event.type === REMOTE_CONTROL_EVENT_TYPE
|
||||||
|
&& event.event.type === EVENT_TYPES.stop
|
||||||
|
&& participant.getId() === this.controlledParticipant) {
|
||||||
|
this._stop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts processing the mouse and keyboard events.
|
* Starts processing the mouse and keyboard events.
|
||||||
* @param {JQuery.selector} area the selector which will be used for
|
|
||||||
* attaching the listeners on.
|
|
||||||
*/
|
*/
|
||||||
start(area) {
|
_start() {
|
||||||
if(!this.enabled)
|
if(!this.enabled)
|
||||||
return;
|
return;
|
||||||
this.area = area;
|
APP.conference.addConferenceListener(
|
||||||
|
ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
|
||||||
|
this.stopListener);
|
||||||
|
this.area = $("#largeVideoWrapper");
|
||||||
this.area.mousemove(event => {
|
this.area.mousemove(event => {
|
||||||
const position = this.area.position();
|
const position = this.area.position();
|
||||||
this._sendRemoteControlEvent({
|
this._sendRemoteControlEvent(this.controlledParticipant, {
|
||||||
type: EVENT_TYPES.mousemove,
|
type: EVENT_TYPES.mousemove,
|
||||||
x: (event.pageX - position.left)/this.area.width(),
|
x: (event.pageX - position.left)/this.area.width(),
|
||||||
y: (event.pageY - position.top)/this.area.height()
|
y: (event.pageY - position.top)/this.area.height()
|
||||||
|
@ -85,7 +167,7 @@ export default class Controller {
|
||||||
this._onMouseClickHandler.bind(this, EVENT_TYPES.mousedblclick));
|
this._onMouseClickHandler.bind(this, EVENT_TYPES.mousedblclick));
|
||||||
this.area.contextmenu(() => false);
|
this.area.contextmenu(() => false);
|
||||||
this.area[0].onmousewheel = event => {
|
this.area[0].onmousewheel = event => {
|
||||||
this._sendRemoteControlEvent({
|
this._sendRemoteControlEvent(this.controlledParticipant, {
|
||||||
type: EVENT_TYPES.mousescroll,
|
type: EVENT_TYPES.mousescroll,
|
||||||
x: event.deltaX,
|
x: event.deltaX,
|
||||||
y: event.deltaY
|
y: event.deltaY
|
||||||
|
@ -99,7 +181,11 @@ export default class Controller {
|
||||||
/**
|
/**
|
||||||
* Stops processing the mouse and keyboard events.
|
* Stops processing the mouse and keyboard events.
|
||||||
*/
|
*/
|
||||||
stop() {
|
_stop() {
|
||||||
|
APP.conference.removeConferenceListener(
|
||||||
|
ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
|
||||||
|
this.stopListener);
|
||||||
|
this.controlledParticipant = null;
|
||||||
this.area.off( "mousemove" );
|
this.area.off( "mousemove" );
|
||||||
this.area.off( "mousedown" );
|
this.area.off( "mousedown" );
|
||||||
this.area.off( "mouseup" );
|
this.area.off( "mouseup" );
|
||||||
|
@ -116,7 +202,7 @@ export default class Controller {
|
||||||
* @param {Event} event the mouse event.
|
* @param {Event} event the mouse event.
|
||||||
*/
|
*/
|
||||||
_onMouseClickHandler(type, event) {
|
_onMouseClickHandler(type, event) {
|
||||||
this._sendRemoteControlEvent({
|
this._sendRemoteControlEvent(this.controlledParticipant, {
|
||||||
type: type,
|
type: type,
|
||||||
button: event.which
|
button: event.which
|
||||||
});
|
});
|
||||||
|
@ -128,25 +214,10 @@ export default class Controller {
|
||||||
* @param {Event} event the key event.
|
* @param {Event} event the key event.
|
||||||
*/
|
*/
|
||||||
_onKeyPessHandler(type, event) {
|
_onKeyPessHandler(type, event) {
|
||||||
this._sendRemoteControlEvent({
|
this._sendRemoteControlEvent(this.controlledParticipant, {
|
||||||
type: type,
|
type: type,
|
||||||
key: getKey(event),
|
key: getKey(event),
|
||||||
modifiers: getModifiers(event),
|
modifiers: getModifiers(event),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends remote control event to the controlled participant.
|
|
||||||
* @param {Object} event the remote control event.
|
|
||||||
*/
|
|
||||||
_sendRemoteControlEvent(event) {
|
|
||||||
if(!this.enabled)
|
|
||||||
return;
|
|
||||||
try{
|
|
||||||
APP.conference.sendEndpointMessage("",
|
|
||||||
{type: API_EVENT_TYPE, event});
|
|
||||||
} catch (e) {
|
|
||||||
// failed to send the event.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
/* global APP, JitsiMeetJS */
|
/* global APP, JitsiMeetJS */
|
||||||
import {DISCO_REMOTE_CONTROL_FEATURE, API_EVENT_TYPE}
|
import {DISCO_REMOTE_CONTROL_FEATURE, REMOTE_CONTROL_EVENT_TYPE, EVENT_TYPES,
|
||||||
from "../../service/remotecontrol/Constants";
|
PERMISSIONS_ACTIONS} from "../../service/remotecontrol/Constants";
|
||||||
|
import RemoteControlParticipant from "./RemoteControlParticipant";
|
||||||
|
|
||||||
const ConferenceEvents = JitsiMeetJS.events.conference;
|
const ConferenceEvents = JitsiMeetJS.events.conference;
|
||||||
|
|
||||||
|
@ -10,13 +11,16 @@ const ConferenceEvents = JitsiMeetJS.events.conference;
|
||||||
* API module. From there the events can be received from wrapper application
|
* API module. From there the events can be received from wrapper application
|
||||||
* and executed.
|
* and executed.
|
||||||
*/
|
*/
|
||||||
export default class Receiver {
|
export default class Receiver extends RemoteControlParticipant {
|
||||||
/**
|
/**
|
||||||
* Creates new instance.
|
* Creates new instance.
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
constructor() {
|
constructor() {
|
||||||
this.enabled = false;
|
super();
|
||||||
|
this.controller = null;
|
||||||
|
this._remoteControlEventsListener
|
||||||
|
= this._onRemoteControlEvent.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -28,17 +32,9 @@ export default class Receiver {
|
||||||
this.enabled = enabled;
|
this.enabled = enabled;
|
||||||
// Announce remote control support.
|
// Announce remote control support.
|
||||||
APP.connection.addFeature(DISCO_REMOTE_CONTROL_FEATURE, true);
|
APP.connection.addFeature(DISCO_REMOTE_CONTROL_FEATURE, true);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attaches listener for ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED events.
|
|
||||||
*/
|
|
||||||
start() {
|
|
||||||
if(this.enabled) {
|
|
||||||
APP.conference.addConferenceListener(
|
APP.conference.addConferenceListener(
|
||||||
ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
|
ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
|
||||||
this._onRemoteControlEvent);
|
this._remoteControlEventsListener);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,7 +45,13 @@ export default class Receiver {
|
||||||
stop() {
|
stop() {
|
||||||
APP.conference.removeConferenceListener(
|
APP.conference.removeConferenceListener(
|
||||||
ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
|
ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
|
||||||
this._onRemoteControlEvent);
|
this._remoteControlEventsListener);
|
||||||
|
const event = {
|
||||||
|
type: EVENT_TYPES.stop
|
||||||
|
};
|
||||||
|
this._sendRemoteControlEvent(this.controller, event);
|
||||||
|
this.controller = null;
|
||||||
|
APP.API.sendRemoteControlEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -58,7 +60,34 @@ export default class Receiver {
|
||||||
* @param {Object} event the remote control event.
|
* @param {Object} event the remote control event.
|
||||||
*/
|
*/
|
||||||
_onRemoteControlEvent(participant, event) {
|
_onRemoteControlEvent(participant, event) {
|
||||||
if(event.type === API_EVENT_TYPE && this.enabled)
|
if(this.enabled && event.type === REMOTE_CONTROL_EVENT_TYPE) {
|
||||||
APP.API.sendRemoteControlEvent(event.event);
|
const remoteControlEvent = event.event;
|
||||||
|
if(this.controller === null
|
||||||
|
&& remoteControlEvent.type === EVENT_TYPES.permissions
|
||||||
|
&& remoteControlEvent.action === PERMISSIONS_ACTIONS.request) {
|
||||||
|
remoteControlEvent.userId = participant.getId();
|
||||||
|
remoteControlEvent.userJID = participant.getJid();
|
||||||
|
remoteControlEvent.displayName = participant.getDisplayName();
|
||||||
|
} else if(this.controller !== participant.getId()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
APP.API.sendRemoteControlEvent(remoteControlEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles remote control permission events received from the API module.
|
||||||
|
* @param {String} userId the user id of the participant related to the
|
||||||
|
* event.
|
||||||
|
* @param {PERMISSIONS_ACTIONS} action the action related to the event.
|
||||||
|
*/
|
||||||
|
_onRemoteControlPermissionsEvent(userId, action) {
|
||||||
|
if(action === PERMISSIONS_ACTIONS.grant) {
|
||||||
|
this.controller = userId;
|
||||||
|
}
|
||||||
|
this._sendRemoteControlEvent(userId, {
|
||||||
|
type: EVENT_TYPES.permissions,
|
||||||
|
action: action
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
/* 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}
|
||||||
|
from "../../service/remotecontrol/Constants";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements the remote control functionality.
|
* Implements the remote control functionality.
|
||||||
|
@ -32,14 +34,28 @@ class RemoteControl {
|
||||||
this.controller.enable(true);
|
this.controller.enable(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles remote control events from the API module.
|
||||||
|
* @param {object} event the remote control event
|
||||||
|
*/
|
||||||
|
onRemoteControlAPIEvent(event) {
|
||||||
|
switch(event.type) {
|
||||||
|
case EVENT_TYPES.supported:
|
||||||
|
this._onRemoteControlSupported();
|
||||||
|
break;
|
||||||
|
case EVENT_TYPES.permissions:
|
||||||
|
this.receiver._onRemoteControlPermissionsEvent(
|
||||||
|
event.userId, event.action);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles API event for support for executing remote control events into
|
* Handles API event for support for executing remote control events into
|
||||||
* the wrapper application.
|
* the wrapper application.
|
||||||
* @param {boolean} isSupported true if the receiver side is supported by
|
|
||||||
* the wrapper application.
|
|
||||||
*/
|
*/
|
||||||
onRemoteControlSupported(isSupported) {
|
_onRemoteControlSupported() {
|
||||||
if(isSupported && !config.disableRemoteControl) {
|
if(!config.disableRemoteControl) {
|
||||||
this.enabled = true;
|
this.enabled = true;
|
||||||
if(this.initialized) {
|
if(this.initialized) {
|
||||||
this.receiver.enable(true);
|
this.receiver.enable(true);
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
/* global APP */
|
||||||
|
import {REMOTE_CONTROL_EVENT_TYPE}
|
||||||
|
from "../../service/remotecontrol/Constants";
|
||||||
|
|
||||||
|
export default class RemoteControlParticipant {
|
||||||
|
/**
|
||||||
|
* Creates new instance.
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
this.enabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables / Disables the remote control
|
||||||
|
* @param {boolean} enabled the new state.
|
||||||
|
*/
|
||||||
|
enable(enabled) {
|
||||||
|
this.enabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends remote control event to other participant trough data channel.
|
||||||
|
* @param {Object} event the remote control event.
|
||||||
|
* @param {Function} onDataChannelFail handler for data channel failure.
|
||||||
|
*/
|
||||||
|
_sendRemoteControlEvent(to, event, onDataChannelFail = () => {}) {
|
||||||
|
if(!this.enabled || !to)
|
||||||
|
return;
|
||||||
|
try{
|
||||||
|
APP.conference.sendEndpointMessage(to,
|
||||||
|
{type: REMOTE_CONTROL_EVENT_TYPE, event});
|
||||||
|
} catch (e) {
|
||||||
|
onDataChannelFail(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,10 +14,22 @@ export const EVENT_TYPES = {
|
||||||
mousedblclick: "mousedblclick",
|
mousedblclick: "mousedblclick",
|
||||||
mousescroll: "mousescroll",
|
mousescroll: "mousescroll",
|
||||||
keydown: "keydown",
|
keydown: "keydown",
|
||||||
keyup: "keyup"
|
keyup: "keyup",
|
||||||
|
permissions: "permissions",
|
||||||
|
stop: "stop",
|
||||||
|
supported: "supported"
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Actions for the remote control permission events.
|
||||||
|
*/
|
||||||
|
export const PERMISSIONS_ACTIONS = {
|
||||||
|
request: "request",
|
||||||
|
grant: "grant",
|
||||||
|
deny: "deny"
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The type of remote control events sent trough the API module.
|
* The type of remote control events sent trough the API module.
|
||||||
*/
|
*/
|
||||||
export const API_EVENT_TYPE = "remote-control-event";
|
export const REMOTE_CONTROL_EVENT_TYPE = "remote-control-event";
|
||||||
|
|
Loading…
Reference in New Issue