Sfoglia il codice sorgente

feat(remotecontrol): Implement requesting remote control permissions

j8
hristoterezov 8 anni fa
parent
commit
846fb9abb0

+ 1
- 4
conference.js Vedi File

@@ -985,8 +985,6 @@ export default {
985 985
         let externalInstallation = false;
986 986
 
987 987
         if (shareScreen) {
988
-            // For remote control testing:
989
-            // remoteControlReceiver.start();
990 988
             createLocalTracks({
991 989
                 devices: ['desktop'],
992 990
                 desktopSharingExtensionExternalInstallation: {
@@ -1076,8 +1074,7 @@ export default {
1076 1074
                     dialogTitleKey, dialogTxt, false);
1077 1075
             });
1078 1076
         } else {
1079
-            // For remote control testing:
1080
-            // remoteControlReceiver.stop();
1077
+            APP.remoteControl.receiver.stop();
1081 1078
             createLocalTracks({ devices: ['video'] }).then(
1082 1079
                 ([stream]) => this.useVideoStream(stream)
1083 1080
             ).then(() => {

+ 2
- 2
modules/API/API.js Vedi File

@@ -56,8 +56,8 @@ function initCommands() {
56 56
         "video-hangup": () => APP.conference.hangup(),
57 57
         "email": APP.conference.changeLocalEmail,
58 58
         "avatar-url": APP.conference.changeLocalAvatarUrl,
59
-        "remote-control-supported": isSupported =>
60
-            APP.remoteControl.onRemoteControlSupported(isSupported)
59
+        "remote-control-event": event =>
60
+            APP.remoteControl.onRemoteControlAPIEvent(event)
61 61
     };
62 62
     Object.keys(commands).forEach(function (key) {
63 63
         postis.listen(key, args => commands[key](...args));

+ 103
- 32
modules/remotecontrol/Controller.js Vedi File

@@ -1,7 +1,10 @@
1
-/* global $, APP */
1
+/* global $, JitsiMeetJS, APP */
2 2
 import * as KeyCodes from "../keycode/keycode";
3
-import {EVENT_TYPES, API_EVENT_TYPE}
3
+import {EVENT_TYPES, REMOTE_CONTROL_EVENT_TYPE, PERMISSIONS_ACTIONS}
4 4
     from "../../service/remotecontrol/Constants";
5
+import RemoteControlParticipant from "./RemoteControlParticipant";
6
+
7
+const ConferenceEvents = JitsiMeetJS.events.conference;
5 8
 
6 9
 /**
7 10
  * Extract the keyboard key from the keyboard event.
@@ -44,34 +47,113 @@ function getModifiers(event) {
44 47
  * It listens for mouse and keyboard events and sends them to the receiver
45 48
  * party of the remote control session.
46 49
  */
47
-export default class Controller {
50
+export default class Controller extends RemoteControlParticipant {
48 51
     /**
49 52
      * Creates new instance.
50 53
      */
51 54
     constructor() {
52
-        this.enabled = false;
55
+        super();
56
+        this.controlledParticipant = null;
57
+        this.requestedParticipant = null;
58
+        this.stopListener = this._handleRemoteControlStoppedEvent.bind(this);
53 59
     }
54 60
 
55 61
     /**
56
-     * Enables / Disables the remote control
57
-     * @param {boolean} enabled the new state.
62
+     * Requests permissions from the remote control receiver side.
63
+     * @param {string} userId the user id of the participant that will be
64
+     * requested.
58 65
      */
59
-    enable(enabled) {
60
-        this.enabled = enabled;
66
+    requestPermissions(userId) {
67
+        if(!this.enabled) {
68
+            return Promise.reject(new Error("Remote control is disabled!"));
69
+        }
70
+        return new Promise((resolve, reject) => {
71
+            let permissionsReplyListener = (participant, event) => {
72
+                let result = null;
73
+                try {
74
+                    result = this._handleReply(participant, event);
75
+                } catch (e) {
76
+                    reject(e);
77
+                }
78
+                if(result !== null) {
79
+                    this.requestedParticipant = null;
80
+                    APP.conference.removeConferenceListener(
81
+                        ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
82
+                        permissionsReplyListener);
83
+                    resolve(result);
84
+                }
85
+            };
86
+            APP.conference.addConferenceListener(
87
+                ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
88
+                permissionsReplyListener);
89
+            this.requestedParticipant = userId;
90
+            this._sendRemoteControlEvent(userId, {
91
+                type: EVENT_TYPES.permissions,
92
+                action: PERMISSIONS_ACTIONS.request
93
+            }, e => {
94
+                APP.conference.removeConferenceListener(
95
+                    ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
96
+                    permissionsReplyListener);
97
+                this.requestedParticipant = null;
98
+                reject(e);
99
+            });
100
+        });
101
+    }
102
+
103
+    /**
104
+     * Handles the reply of the permissions request.
105
+     * @param {JitsiParticipant} participant the participant that has sent the
106
+     * reply
107
+     * @param {object} event the remote control event.
108
+     */
109
+    _handleReply(participant, event) {
110
+        const remoteControlEvent = event.event;
111
+        const userId = participant.getId();
112
+        if(this.enabled && event.type === REMOTE_CONTROL_EVENT_TYPE
113
+            && remoteControlEvent.type === EVENT_TYPES.permissions
114
+            && userId === this.requestedParticipant) {
115
+            if(remoteControlEvent.action === PERMISSIONS_ACTIONS.grant) {
116
+                this.controlledParticipant = userId;
117
+                this._start();
118
+                return true;
119
+            } else if(remoteControlEvent.action === PERMISSIONS_ACTIONS.deny) {
120
+                return false;
121
+            } else {
122
+                throw new Error("Unknown reply received!");
123
+            }
124
+        } else {
125
+            //different message type or another user -> ignoring the message
126
+            return null;
127
+        }
128
+    }
129
+
130
+    /**
131
+     * Handles remote control stopped.
132
+     * @param {JitsiParticipant} participant the participant that has sent the
133
+     * event
134
+     * @param {object} event the the remote control event.
135
+     */
136
+    _handleRemoteControlStoppedEvent(participant, event) {
137
+        if(this.enabled && event.type === REMOTE_CONTROL_EVENT_TYPE
138
+            && event.event.type === EVENT_TYPES.stop
139
+            && participant.getId() === this.controlledParticipant) {
140
+            this._stop();
141
+        }
61 142
     }
62 143
 
63 144
     /**
64 145
      * Starts processing the mouse and keyboard events.
65
-     * @param {JQuery.selector} area the selector which will be used for
66
-     * attaching the listeners on.
67 146
      */
68
-    start(area) {
147
+    _start() {
69 148
         if(!this.enabled)
70 149
             return;
71
-        this.area = area;
150
+        APP.conference.addConferenceListener(
151
+            ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
152
+            this.stopListener);
153
+        this.area = $("#largeVideoWrapper");
72 154
         this.area.mousemove(event => {
73 155
             const position = this.area.position();
74
-            this._sendRemoteControlEvent({
156
+            this._sendRemoteControlEvent(this.controlledParticipant, {
75 157
                 type: EVENT_TYPES.mousemove,
76 158
                 x: (event.pageX - position.left)/this.area.width(),
77 159
                 y: (event.pageY - position.top)/this.area.height()
@@ -85,7 +167,7 @@ export default class Controller {
85 167
             this._onMouseClickHandler.bind(this, EVENT_TYPES.mousedblclick));
86 168
         this.area.contextmenu(() => false);
87 169
         this.area[0].onmousewheel = event => {
88
-            this._sendRemoteControlEvent({
170
+            this._sendRemoteControlEvent(this.controlledParticipant, {
89 171
                 type: EVENT_TYPES.mousescroll,
90 172
                 x: event.deltaX,
91 173
                 y: event.deltaY
@@ -99,7 +181,11 @@ export default class Controller {
99 181
     /**
100 182
      * Stops processing the mouse and keyboard events.
101 183
      */
102
-    stop() {
184
+    _stop() {
185
+        APP.conference.removeConferenceListener(
186
+            ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
187
+            this.stopListener);
188
+        this.controlledParticipant = null;
103 189
         this.area.off( "mousemove" );
104 190
         this.area.off( "mousedown" );
105 191
         this.area.off( "mouseup" );
@@ -116,7 +202,7 @@ export default class Controller {
116 202
      * @param {Event} event the mouse event.
117 203
      */
118 204
     _onMouseClickHandler(type, event) {
119
-        this._sendRemoteControlEvent({
205
+        this._sendRemoteControlEvent(this.controlledParticipant, {
120 206
             type: type,
121 207
             button: event.which
122 208
         });
@@ -128,25 +214,10 @@ export default class Controller {
128 214
      * @param {Event} event the key event.
129 215
      */
130 216
     _onKeyPessHandler(type, event) {
131
-        this._sendRemoteControlEvent({
217
+        this._sendRemoteControlEvent(this.controlledParticipant, {
132 218
             type: type,
133 219
             key: getKey(event),
134 220
             modifiers: getModifiers(event),
135 221
         });
136 222
     }
137
-
138
-    /**
139
-     * Sends remote control event to the controlled participant.
140
-     * @param {Object} event the remote control event.
141
-     */
142
-    _sendRemoteControlEvent(event) {
143
-        if(!this.enabled)
144
-            return;
145
-        try{
146
-            APP.conference.sendEndpointMessage("",
147
-                {type: API_EVENT_TYPE, event});
148
-        } catch (e) {
149
-            // failed to send the event.
150
-        }
151
-    }
152 223
 }

+ 45
- 16
modules/remotecontrol/Receiver.js Vedi File

@@ -1,6 +1,7 @@
1 1
 /* global APP, JitsiMeetJS */
2
-import {DISCO_REMOTE_CONTROL_FEATURE, API_EVENT_TYPE}
3
-    from "../../service/remotecontrol/Constants";
2
+import {DISCO_REMOTE_CONTROL_FEATURE, REMOTE_CONTROL_EVENT_TYPE, EVENT_TYPES,
3
+    PERMISSIONS_ACTIONS} from "../../service/remotecontrol/Constants";
4
+import RemoteControlParticipant from "./RemoteControlParticipant";
4 5
 
5 6
 const ConferenceEvents = JitsiMeetJS.events.conference;
6 7
 
@@ -10,13 +11,16 @@ const ConferenceEvents = JitsiMeetJS.events.conference;
10 11
  * API module. From there the events can be received from wrapper application
11 12
  * and executed.
12 13
  */
13
-export default class Receiver {
14
+export default class Receiver extends RemoteControlParticipant {
14 15
     /**
15 16
      * Creates new instance.
16 17
      * @constructor
17 18
      */
18 19
     constructor() {
19
-        this.enabled = false;
20
+        super();
21
+        this.controller = null;
22
+        this._remoteControlEventsListener
23
+            = this._onRemoteControlEvent.bind(this);
20 24
     }
21 25
 
22 26
     /**
@@ -28,17 +32,9 @@ export default class Receiver {
28 32
             this.enabled = enabled;
29 33
             // Announce remote control support.
30 34
             APP.connection.addFeature(DISCO_REMOTE_CONTROL_FEATURE, true);
31
-        }
32
-    }
33
-
34
-    /**
35
-     * Attaches listener for ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED events.
36
-     */
37
-    start() {
38
-        if(this.enabled) {
39 35
             APP.conference.addConferenceListener(
40 36
                 ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
41
-                this._onRemoteControlEvent);
37
+                this._remoteControlEventsListener);
42 38
         }
43 39
     }
44 40
 
@@ -49,7 +45,13 @@ export default class Receiver {
49 45
     stop() {
50 46
         APP.conference.removeConferenceListener(
51 47
             ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
52
-            this._onRemoteControlEvent);
48
+            this._remoteControlEventsListener);
49
+        const event = {
50
+            type: EVENT_TYPES.stop
51
+        };
52
+        this._sendRemoteControlEvent(this.controller, event);
53
+        this.controller = null;
54
+        APP.API.sendRemoteControlEvent(event);
53 55
     }
54 56
 
55 57
     /**
@@ -58,7 +60,34 @@ export default class Receiver {
58 60
      * @param {Object} event the remote control event.
59 61
      */
60 62
     _onRemoteControlEvent(participant, event) {
61
-        if(event.type === API_EVENT_TYPE && this.enabled)
62
-            APP.API.sendRemoteControlEvent(event.event);
63
+        if(this.enabled && event.type === REMOTE_CONTROL_EVENT_TYPE) {
64
+            const remoteControlEvent = event.event;
65
+            if(this.controller === null
66
+                && remoteControlEvent.type === EVENT_TYPES.permissions
67
+                && remoteControlEvent.action === PERMISSIONS_ACTIONS.request) {
68
+                remoteControlEvent.userId = participant.getId();
69
+                remoteControlEvent.userJID = participant.getJid();
70
+                remoteControlEvent.displayName = participant.getDisplayName();
71
+            } else if(this.controller !== participant.getId()) {
72
+                return;
73
+            }
74
+            APP.API.sendRemoteControlEvent(remoteControlEvent);
75
+        }
76
+    }
77
+
78
+    /**
79
+     * Handles remote control permission events received from the API module.
80
+     * @param {String} userId the user id of the participant related to the
81
+     * event.
82
+     * @param {PERMISSIONS_ACTIONS} action the action related to the event.
83
+     */
84
+    _onRemoteControlPermissionsEvent(userId, action) {
85
+        if(action === PERMISSIONS_ACTIONS.grant) {
86
+            this.controller = userId;
87
+        }
88
+        this._sendRemoteControlEvent(userId, {
89
+            type: EVENT_TYPES.permissions,
90
+            action: action
91
+        });
63 92
     }
64 93
 }

+ 20
- 4
modules/remotecontrol/RemoteControl.js Vedi File

@@ -1,6 +1,8 @@
1 1
 /* global APP, config */
2 2
 import Controller from "./Controller";
3 3
 import Receiver from "./Receiver";
4
+import {EVENT_TYPES}
5
+    from "../../service/remotecontrol/Constants";
4 6
 
5 7
 /**
6 8
  * Implements the remote control functionality.
@@ -32,14 +34,28 @@ class RemoteControl {
32 34
         this.controller.enable(true);
33 35
     }
34 36
 
37
+    /**
38
+     * Handles remote control events from the API module.
39
+     * @param {object} event the remote control event
40
+     */
41
+    onRemoteControlAPIEvent(event) {
42
+        switch(event.type) {
43
+            case EVENT_TYPES.supported:
44
+                this._onRemoteControlSupported();
45
+                break;
46
+            case EVENT_TYPES.permissions:
47
+                this.receiver._onRemoteControlPermissionsEvent(
48
+                    event.userId, event.action);
49
+                break;
50
+        }
51
+    }
52
+
35 53
     /**
36 54
      * Handles API event for support for executing remote control events into
37 55
      * the wrapper application.
38
-     * @param {boolean} isSupported true if the receiver side is supported by
39
-     * the wrapper application.
40 56
      */
41
-    onRemoteControlSupported(isSupported) {
42
-        if(isSupported && !config.disableRemoteControl) {
57
+    _onRemoteControlSupported() {
58
+        if(!config.disableRemoteControl) {
43 59
             this.enabled = true;
44 60
             if(this.initialized) {
45 61
                 this.receiver.enable(true);

+ 36
- 0
modules/remotecontrol/RemoteControlParticipant.js Vedi File

@@ -0,0 +1,36 @@
1
+/* global APP */
2
+import {REMOTE_CONTROL_EVENT_TYPE}
3
+    from "../../service/remotecontrol/Constants";
4
+
5
+export default class RemoteControlParticipant {
6
+    /**
7
+     * Creates new instance.
8
+     */
9
+    constructor() {
10
+        this.enabled = false;
11
+    }
12
+
13
+    /**
14
+     * Enables / Disables the remote control
15
+     * @param {boolean} enabled the new state.
16
+     */
17
+    enable(enabled) {
18
+        this.enabled = enabled;
19
+    }
20
+
21
+    /**
22
+     * Sends remote control event to other participant trough data channel.
23
+     * @param {Object} event the remote control event.
24
+     * @param {Function} onDataChannelFail handler for data channel failure.
25
+     */
26
+    _sendRemoteControlEvent(to, event, onDataChannelFail = () => {}) {
27
+        if(!this.enabled || !to)
28
+            return;
29
+        try{
30
+            APP.conference.sendEndpointMessage(to,
31
+                {type: REMOTE_CONTROL_EVENT_TYPE, event});
32
+        } catch (e) {
33
+            onDataChannelFail(e);
34
+        }
35
+    }
36
+}

+ 14
- 2
service/remotecontrol/Constants.js Vedi File

@@ -14,10 +14,22 @@ export const EVENT_TYPES = {
14 14
     mousedblclick: "mousedblclick",
15 15
     mousescroll: "mousescroll",
16 16
     keydown: "keydown",
17
-    keyup: "keyup"
17
+    keyup: "keyup",
18
+    permissions: "permissions",
19
+    stop: "stop",
20
+    supported: "supported"
21
+};
22
+
23
+/**
24
+ * Actions for the remote control permission events.
25
+ */
26
+export const PERMISSIONS_ACTIONS = {
27
+    request: "request",
28
+    grant: "grant",
29
+    deny: "deny"
18 30
 };
19 31
 
20 32
 /**
21 33
  * The type of remote control events sent trough the API module.
22 34
  */
23
-export const API_EVENT_TYPE = "remote-control-event";
35
+export const REMOTE_CONTROL_EVENT_TYPE = "remote-control-event";

Loading…
Annulla
Salva