Browse Source

feat(remotecontrol): Implement basic remote control support

master
hristoterezov 9 years ago
parent
commit
896650d005
6 changed files with 387 additions and 4 deletions
  1. 2
    0
      app.js
  2. 34
    4
      conference.js
  3. 8
    0
      modules/API/API.js
  4. 160
    0
      modules/keycode/keycode.js
  5. 136
    0
      modules/remotecontrol/Controller.js
  6. 47
    0
      modules/remotecontrol/Receiver.js

+ 2
- 0
app.js View File

@@ -22,6 +22,8 @@ import conference from './conference';
22 22
 import API from './modules/API/API';
23 23
 
24 24
 import translation from "./modules/translation/translation";
25
+// For remote control testing:
26
+// import remoteControlController from "./modules/remotecontrol/Controller";
25 27
 
26 28
 const APP = {
27 29
     // Used by do_external_connect.js if we receive the attach data after

+ 34
- 4
conference.js View File

@@ -17,6 +17,9 @@ import UIUtil from './modules/UI/util/UIUtil';
17 17
 
18 18
 import analytics from './modules/analytics/analytics';
19 19
 
20
+// For remote control testing:
21
+// import remoteControlReceiver from './modules/remotecontrol/Receiver';
22
+
20 23
 const ConnectionEvents = JitsiMeetJS.events.connection;
21 24
 const ConnectionErrors = JitsiMeetJS.errors.connection;
22 25
 
@@ -981,6 +984,8 @@ export default {
981 984
         let externalInstallation = false;
982 985
 
983 986
         if (shareScreen) {
987
+            // For remote control testing:
988
+            // remoteControlReceiver.start();
984 989
             createLocalTracks({
985 990
                 devices: ['desktop'],
986 991
                 desktopSharingExtensionExternalInstallation: {
@@ -1070,6 +1075,8 @@ export default {
1070 1075
                     dialogTitleKey, dialogTxt, false);
1071 1076
             });
1072 1077
         } else {
1078
+            // For remote control testing:
1079
+            // remoteControlReceiver.stop();
1073 1080
             createLocalTracks({ devices: ['video'] }).then(
1074 1081
                 ([stream]) => this.useVideoStream(stream)
1075 1082
             ).then(() => {
@@ -1600,12 +1607,23 @@ export default {
1600 1607
     },
1601 1608
     /**
1602 1609
     * Adds any room listener.
1603
-    * @param eventName one of the ConferenceEvents
1604
-    * @param callBack the function to be called when the event occurs
1610
+    * @param {string} eventName one of the ConferenceEvents
1611
+    * @param {Function} listener the function to be called when the event
1612
+    * occurs
1613
+    */
1614
+    addConferenceListener(eventName, listener) {
1615
+        room.on(eventName, listener);
1616
+    },
1617
+
1618
+    /**
1619
+    * Removes any room listener.
1620
+    * @param {string} eventName one of the ConferenceEvents
1621
+    * @param {Function} listener the listener to be removed.
1605 1622
     */
1606
-    addConferenceListener(eventName, callBack) {
1607
-        room.on(eventName, callBack);
1623
+    removeConferenceListener(eventName, listener) {
1624
+        room.off(eventName, listener);
1608 1625
     },
1626
+
1609 1627
     /**
1610 1628
      * Inits list of current devices and event listener for device change.
1611 1629
      * @private
@@ -1813,5 +1831,17 @@ export default {
1813 1831
         APP.settings.setAvatarUrl(url);
1814 1832
         APP.UI.setUserAvatarUrl(room.myUserId(), url);
1815 1833
         sendData(commands.AVATAR_URL, url);
1834
+    },
1835
+
1836
+    /**
1837
+     * Sends a message via the data channel.
1838
+     * @param to {string} the id of the endpoint that should receive the
1839
+     * message. If "" the message will be sent to all participants.
1840
+     * @param payload {object} the payload of the message.
1841
+     * @throws NetworkError or InvalidStateError or Error if the operation
1842
+     * fails.
1843
+     */
1844
+    sendEndpointMessage (to, payload) {
1845
+        room.sendEndpointMessage(to, payload);
1816 1846
     }
1817 1847
 };

+ 8
- 0
modules/API/API.js View File

@@ -201,6 +201,14 @@ export default {
201 201
         triggerEvent("video-ready-to-close", {});
202 202
     },
203 203
 
204
+    /**
205
+     * Sends remote control event.
206
+     * @param {object} event the event.
207
+     */
208
+    sendRemoteControlEvent(event) {
209
+        sendMessage({method: "remote-control-event", params: event});
210
+    },
211
+
204 212
     /**
205 213
      * Removes the listeners.
206 214
      */

+ 160
- 0
modules/keycode/keycode.js View File

@@ -0,0 +1,160 @@
1
+/**
2
+ * Enumerates the supported keys.
3
+ */
4
+export const KEYS = {
5
+    BACKSPACE: "backspace" ,
6
+    DELETE : "delete",
7
+    RETURN : "enter",
8
+    TAB : "tab",
9
+    ESCAPE : "escape",
10
+    UP : "up",
11
+    DOWN : "down",
12
+    RIGHT : "right",
13
+    LEFT : "left",
14
+    HOME : "home",
15
+    END : "end",
16
+    PAGEUP : "pageup",
17
+    PAGEDOWN : "pagedown",
18
+
19
+    F1 : "f1",
20
+    F2 : "f2",
21
+    F3 : "f3",
22
+    F4 : "f4",
23
+    F5 : "f5",
24
+    F6 : "f6",
25
+    F7 : "f7",
26
+    F8 : "f8",
27
+    F9 : "f9",
28
+    F10 : "f10",
29
+    F11 : "f11",
30
+    F12 : "f12",
31
+    META : "command",
32
+    CMD_L: "command",
33
+    CMD_R: "command",
34
+    ALT : "alt",
35
+    CONTROL : "control",
36
+    SHIFT : "shift",
37
+    CAPS_LOCK: "caps_lock", //not supported by robotjs
38
+    SPACE : "space",
39
+    PRINTSCREEN : "printscreen",
40
+    INSERT : "insert",
41
+
42
+    NUMPAD_0 : "numpad_0",
43
+    NUMPAD_1 : "numpad_1",
44
+    NUMPAD_2 : "numpad_2",
45
+    NUMPAD_3 : "numpad_3",
46
+    NUMPAD_4 : "numpad_4",
47
+    NUMPAD_5 : "numpad_5",
48
+    NUMPAD_6 : "numpad_6",
49
+    NUMPAD_7 : "numpad_7",
50
+    NUMPAD_8 : "numpad_8",
51
+    NUMPAD_9 : "numpad_9",
52
+
53
+    COMMA: ",",
54
+
55
+    PERIOD: ".",
56
+    SEMICOLON: ";",
57
+    QUOTE: "'",
58
+    BRACKET_LEFT: "[",
59
+    BRACKET_RIGHT: "]",
60
+    BACKQUOTE: "`",
61
+    BACKSLASH: "\\",
62
+    MINUS: "-",
63
+    EQUAL: "=",
64
+    SLASH: "/"
65
+};
66
+
67
+/**
68
+ * Mapping between the key codes and keys deined in KEYS.
69
+ * The mappings are based on
70
+ * https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/keyCode#Specifications
71
+ */
72
+let keyCodeToKey = {
73
+    8: KEYS.BACKSPACE,
74
+    9: KEYS.TAB,
75
+    13: KEYS.RETURN,
76
+    16: KEYS.SHIFT,
77
+    17: KEYS.CONTROL,
78
+    18: KEYS.ALT,
79
+    20: KEYS.CAPS_LOCK,
80
+    27: KEYS.ESCAPE,
81
+    32: KEYS.SPACE,
82
+    33: KEYS.PAGEUP,
83
+    34: KEYS.PAGEDOWN,
84
+    35: KEYS.END,
85
+    36: KEYS.HOME,
86
+    37: KEYS.LEFT,
87
+    38: KEYS.UP,
88
+    39: KEYS.RIGHT,
89
+    40: KEYS.DOWN,
90
+    42: KEYS.PRINTSCREEN,
91
+    44: KEYS.PRINTSCREEN,
92
+    45: KEYS.INSERT,
93
+    46: KEYS.DELETE,
94
+    59: KEYS.SEMICOLON,
95
+    61: KEYS.EQUAL,
96
+    91: KEYS.CMD_L,
97
+    92: KEYS.CMD_R,
98
+    93: KEYS.CMD_R,
99
+    96: KEYS.NUMPAD_0,
100
+    97: KEYS.NUMPAD_1,
101
+    98: KEYS.NUMPAD_2,
102
+    99: KEYS.NUMPAD_3,
103
+    100: KEYS.NUMPAD_4,
104
+    101: KEYS.NUMPAD_5,
105
+    102: KEYS.NUMPAD_6,
106
+    103: KEYS.NUMPAD_7,
107
+    104: KEYS.NUMPAD_8,
108
+    105: KEYS.NUMPAD_9,
109
+    112: KEYS.F1,
110
+    113: KEYS.F2,
111
+    114: KEYS.F3,
112
+    115: KEYS.F4,
113
+    116: KEYS.F5,
114
+    117: KEYS.F6,
115
+    118: KEYS.F7,
116
+    119: KEYS.F8,
117
+    120: KEYS.F9,
118
+    121: KEYS.F10,
119
+    122: KEYS.F11,
120
+    123: KEYS.F12,
121
+    124: KEYS.PRINTSCREEN,
122
+    173: KEYS.MINUS,
123
+    186: KEYS.SEMICOLON,
124
+    187: KEYS.EQUAL,
125
+    188: KEYS.COMMA,
126
+    189: KEYS.MINUS,
127
+    190: KEYS.PERIOD,
128
+    191: KEYS.SLASH,
129
+    192: KEYS.BACKQUOTE,
130
+    219: KEYS.BRACKET_LEFT,
131
+    220: KEYS.BACKSLASH,
132
+    221: KEYS.BRACKET_RIGHT,
133
+    222: KEYS.QUOTE,
134
+    224: KEYS.META,
135
+    229: KEYS.SEMICOLON
136
+};
137
+
138
+/**
139
+ * Generate codes for digit keys (0-9)
140
+ */
141
+for(let i = 0; i < 10; i++) {
142
+    keyCodeToKey[i + 48] = `${i}`;
143
+}
144
+
145
+/**
146
+ * Generate codes for letter keys (a-z)
147
+ */
148
+for(let i = 0; i < 26; i++) {
149
+    let keyCode = i + 65;
150
+    keyCodeToKey[keyCode] = String.fromCharCode(keyCode).toLowerCase();
151
+}
152
+
153
+/**
154
+ * Returns key associated with the keyCode from the passed event.
155
+ * @param {KeyboardEvent} event the event
156
+ * @returns {KEYS} the key on the keyboard.
157
+ */
158
+export function keyboardEventToKey(event) {
159
+    return keyCodeToKey[event.which];
160
+}

+ 136
- 0
modules/remotecontrol/Controller.js View File

@@ -0,0 +1,136 @@
1
+/* global $, APP */
2
+import * as KeyCodes from "../keycode/keycode";
3
+
4
+/**
5
+ * Extract the keyboard key from the keyboard event.
6
+ * @param event {KeyboardEvent} the event.
7
+ * @returns {KEYS} the key that is pressed or undefined.
8
+ */
9
+function getKey(event) {
10
+    return KeyCodes.keyboardEventToKey(event);
11
+}
12
+
13
+/**
14
+ * Extract the modifiers from the keyboard event.
15
+ * @param event {KeyboardEvent} the event.
16
+ * @returns {Array} with possible values: "shift", "control", "alt", "command".
17
+ */
18
+function getModifiers(event) {
19
+    let modifiers = [];
20
+    if(event.shiftKey) {
21
+        modifiers.push("shift");
22
+    }
23
+
24
+    if(event.ctrlKey) {
25
+        modifiers.push("control");
26
+    }
27
+
28
+
29
+    if(event.altKey) {
30
+        modifiers.push("alt");
31
+    }
32
+
33
+    if(event.metaKey) {
34
+        modifiers.push("command");
35
+    }
36
+
37
+    return modifiers;
38
+}
39
+
40
+/**
41
+ * This class represents the controller party for a remote controller session.
42
+ * It listens for mouse and keyboard events and sends them to the receiver
43
+ * party of the remote control session.
44
+ */
45
+class Controller {
46
+    /**
47
+     * Creates new instance.
48
+     */
49
+    constructor() {}
50
+
51
+    /**
52
+     * Starts processing the mouse and keyboard events.
53
+     * @param {JQuery.selector} area the selector which will be used for
54
+     * attaching the listeners on.
55
+     */
56
+    start(area) {
57
+        this.area = area;
58
+        this.area.mousemove(event => {
59
+            const position = this.area.position();
60
+            this._sendEvent({
61
+                type: "mousemove",
62
+                x: (event.pageX - position.left)/this.area.width(),
63
+                y: (event.pageY - position.top)/this.area.height()
64
+            });
65
+        });
66
+        this.area.mousedown(this._onMouseClickHandler.bind(this, "mousedown"));
67
+        this.area.mouseup(this._onMouseClickHandler.bind(this, "mouseup"));
68
+        this.area.dblclick(
69
+            this._onMouseClickHandler.bind(this, "mousedblclick"));
70
+        this.area.contextmenu(() => false);
71
+        this.area[0].onmousewheel = event => {
72
+            this._sendEvent({
73
+                type: "mousescroll",
74
+                x: event.deltaX,
75
+                y: event.deltaY
76
+            });
77
+        };
78
+        $(window).keydown(this._onKeyPessHandler.bind(this, "keydown"));
79
+        $(window).keyup(this._onKeyPessHandler.bind(this, "keyup"));
80
+    }
81
+
82
+    /**
83
+     * Stops processing the mouse and keyboard events.
84
+     */
85
+    stop() {
86
+        this.area.off( "mousemove" );
87
+        this.area.off( "mousedown" );
88
+        this.area.off( "mouseup" );
89
+        this.area.off( "contextmenu" );
90
+        this.area.off( "dblclick" );
91
+        $(window).off( "keydown");
92
+        $(window).off( "keyup");
93
+        this.area[0].onmousewheel = undefined;
94
+    }
95
+
96
+    /**
97
+     * Handler for mouse click events.
98
+     * @param {String} type the type of event ("mousedown"/"mouseup")
99
+     * @param {Event} event the mouse event.
100
+     */
101
+    _onMouseClickHandler(type, event) {
102
+        this._sendEvent({
103
+            type: type,
104
+            button: event.which
105
+        });
106
+    }
107
+
108
+    /**
109
+     * Handler for key press events.
110
+     * @param {String} type the type of event ("keydown"/"keyup")
111
+     * @param {Event} event the key event.
112
+     */
113
+    _onKeyPessHandler(type, event) {
114
+        this._sendEvent({
115
+            type: type,
116
+            key: getKey(event),
117
+            modifiers: getModifiers(event),
118
+        });
119
+    }
120
+
121
+    /**
122
+     * Sends remote control event to the controlled participant.
123
+     * @param {Object} event the remote control event.
124
+     */
125
+    _sendRemoteControlEvent(event) {
126
+        try{
127
+            APP.conference.sendEndpointMessage("",
128
+                {type: "remote-control-event", event});
129
+        } catch (e) {
130
+            // failed to send the event.
131
+        }
132
+    }
133
+}
134
+
135
+
136
+export default new Controller();

+ 47
- 0
modules/remotecontrol/Receiver.js View File

@@ -0,0 +1,47 @@
1
+/* global APP, JitsiMeetJS */
2
+const ConferenceEvents = JitsiMeetJS.events.conference;
3
+
4
+/**
5
+ * This class represents the receiver party for a remote controller session.
6
+ * It handles "remote-control-event" events and sends them to the
7
+ * API module. From there the events can be received from wrapper application
8
+ * and executed.
9
+ */
10
+class Receiver {
11
+    /**
12
+     * Creates new instance.
13
+     * @constructor
14
+     */
15
+    constructor() {}
16
+
17
+    /**
18
+     * Attaches listener for ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED events.
19
+     */
20
+    start() {
21
+        APP.conference.addConferenceListener(
22
+            ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
23
+            this._onRemoteControlEvent);
24
+    }
25
+
26
+    /**
27
+     * Removes the listener for ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED
28
+     * events.
29
+     */
30
+    stop() {
31
+        APP.conference.removeConferenceListener(
32
+            ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
33
+            this._onRemoteControlEvent);
34
+    }
35
+
36
+    /**
37
+     * Sends "remote-control-event" events to to the API module.
38
+     * @param {JitsiParticipant} participant the controller participant
39
+     * @param {Object} event the remote control event.
40
+     */
41
+    _onRemoteControlEvent(participant, event) {
42
+        if(event.type === "remote-control-event")
43
+            APP.API.sendRemoteControlEvent(event.event);
44
+    }
45
+}
46
+
47
+export default new Receiver();

Loading…
Cancel
Save