You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Controller.js 9.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. /* global $, JitsiMeetJS, APP */
  2. import * as KeyCodes from "../keycode/keycode";
  3. import {EVENT_TYPES, REMOTE_CONTROL_EVENT_TYPE, PERMISSIONS_ACTIONS}
  4. from "../../service/remotecontrol/Constants";
  5. import RemoteControlParticipant from "./RemoteControlParticipant";
  6. const ConferenceEvents = JitsiMeetJS.events.conference;
  7. /**
  8. * Extract the keyboard key from the keyboard event.
  9. * @param event {KeyboardEvent} the event.
  10. * @returns {KEYS} the key that is pressed or undefined.
  11. */
  12. function getKey(event) {
  13. return KeyCodes.keyboardEventToKey(event);
  14. }
  15. /**
  16. * Extract the modifiers from the keyboard event.
  17. * @param event {KeyboardEvent} the event.
  18. * @returns {Array} with possible values: "shift", "control", "alt", "command".
  19. */
  20. function getModifiers(event) {
  21. let modifiers = [];
  22. if(event.shiftKey) {
  23. modifiers.push("shift");
  24. }
  25. if(event.ctrlKey) {
  26. modifiers.push("control");
  27. }
  28. if(event.altKey) {
  29. modifiers.push("alt");
  30. }
  31. if(event.metaKey) {
  32. modifiers.push("command");
  33. }
  34. return modifiers;
  35. }
  36. /**
  37. * This class represents the controller party for a remote controller session.
  38. * It listens for mouse and keyboard events and sends them to the receiver
  39. * party of the remote control session.
  40. */
  41. export default class Controller extends RemoteControlParticipant {
  42. /**
  43. * Creates new instance.
  44. */
  45. constructor() {
  46. super();
  47. this.controlledParticipant = null;
  48. this.requestedParticipant = null;
  49. this._stopListener = this._handleRemoteControlStoppedEvent.bind(this);
  50. this._userLeftListener = this._onUserLeft.bind(this);
  51. }
  52. /**
  53. * Requests permissions from the remote control receiver side.
  54. * @param {string} userId the user id of the participant that will be
  55. * requested.
  56. * @returns {Promise<boolean>} - resolve values:
  57. * true - accept
  58. * false - deny
  59. * null - the participant has left.
  60. */
  61. requestPermissions(userId) {
  62. if(!this.enabled) {
  63. return Promise.reject(new Error("Remote control is disabled!"));
  64. }
  65. return new Promise((resolve, reject) => {
  66. const clearRequest = () => {
  67. this.requestedParticipant = null;
  68. APP.conference.removeConferenceListener(
  69. ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
  70. permissionsReplyListener);
  71. APP.conference.removeConferenceListener(
  72. ConferenceEvents.USER_LEFT,
  73. onUserLeft);
  74. };
  75. const permissionsReplyListener = (participant, event) => {
  76. let result = null;
  77. try {
  78. result = this._handleReply(participant, event);
  79. } catch (e) {
  80. reject(e);
  81. }
  82. if(result !== null) {
  83. clearRequest();
  84. resolve(result);
  85. }
  86. };
  87. const onUserLeft = (id) => {
  88. if(id === this.requestedParticipant) {
  89. clearRequest();
  90. resolve(null);
  91. }
  92. };
  93. APP.conference.addConferenceListener(
  94. ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
  95. permissionsReplyListener);
  96. APP.conference.addConferenceListener(ConferenceEvents.USER_LEFT,
  97. onUserLeft);
  98. this.requestedParticipant = userId;
  99. this._sendRemoteControlEvent(userId, {
  100. type: EVENT_TYPES.permissions,
  101. action: PERMISSIONS_ACTIONS.request
  102. }, e => {
  103. clearRequest();
  104. reject(e);
  105. });
  106. });
  107. }
  108. /**
  109. * Handles the reply of the permissions request.
  110. * @param {JitsiParticipant} participant the participant that has sent the
  111. * reply
  112. * @param {object} event the remote control event.
  113. */
  114. _handleReply(participant, event) {
  115. const remoteControlEvent = event.event;
  116. const userId = participant.getId();
  117. if(this.enabled && event.type === REMOTE_CONTROL_EVENT_TYPE
  118. && remoteControlEvent.type === EVENT_TYPES.permissions
  119. && userId === this.requestedParticipant) {
  120. switch(remoteControlEvent.action) {
  121. case PERMISSIONS_ACTIONS.grant: {
  122. this.controlledParticipant = userId;
  123. this._start();
  124. return true;
  125. }
  126. case PERMISSIONS_ACTIONS.deny:
  127. return false;
  128. case PERMISSIONS_ACTIONS.error:
  129. throw new Error("Error occurred on receiver side");
  130. default:
  131. throw new Error("Unknown reply received!");
  132. }
  133. } else {
  134. //different message type or another user -> ignoring the message
  135. return null;
  136. }
  137. }
  138. /**
  139. * Handles remote control stopped.
  140. * @param {JitsiParticipant} participant the participant that has sent the
  141. * event
  142. * @param {object} event the the remote control event.
  143. */
  144. _handleRemoteControlStoppedEvent(participant, event) {
  145. if(this.enabled && event.type === REMOTE_CONTROL_EVENT_TYPE
  146. && event.event.type === EVENT_TYPES.stop
  147. && participant.getId() === this.controlledParticipant) {
  148. this._stop();
  149. }
  150. }
  151. /**
  152. * Starts processing the mouse and keyboard events.
  153. */
  154. _start() {
  155. if(!this.enabled)
  156. return;
  157. APP.conference.addConferenceListener(
  158. ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
  159. this._stopListener);
  160. APP.conference.addConferenceListener(ConferenceEvents.USER_LEFT,
  161. this._userLeftListener);
  162. this.area = $("#largeVideoWrapper");
  163. this.area.mousemove(event => {
  164. const position = this.area.position();
  165. this._sendRemoteControlEvent(this.controlledParticipant, {
  166. type: EVENT_TYPES.mousemove,
  167. x: (event.pageX - position.left)/this.area.width(),
  168. y: (event.pageY - position.top)/this.area.height()
  169. });
  170. });
  171. this.area.mousedown(this._onMouseClickHandler.bind(this,
  172. EVENT_TYPES.mousedown));
  173. this.area.mouseup(this._onMouseClickHandler.bind(this,
  174. EVENT_TYPES.mouseup));
  175. this.area.dblclick(
  176. this._onMouseClickHandler.bind(this, EVENT_TYPES.mousedblclick));
  177. this.area.contextmenu(() => false);
  178. this.area[0].onmousewheel = event => {
  179. this._sendRemoteControlEvent(this.controlledParticipant, {
  180. type: EVENT_TYPES.mousescroll,
  181. x: event.deltaX,
  182. y: event.deltaY
  183. });
  184. };
  185. $(window).keydown(this._onKeyPessHandler.bind(this,
  186. EVENT_TYPES.keydown));
  187. $(window).keyup(this._onKeyPessHandler.bind(this, EVENT_TYPES.keyup));
  188. }
  189. /**
  190. * Stops processing the mouse and keyboard events. Removes added listeners.
  191. */
  192. _stop() {
  193. if(!this.controlledParticipant) {
  194. return;
  195. }
  196. APP.conference.removeConferenceListener(
  197. ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
  198. this._stopListener);
  199. APP.conference.removeConferenceListener(ConferenceEvents.USER_LEFT,
  200. this._userLeftListener);
  201. this.controlledParticipant = null;
  202. this.area.off( "mousemove" );
  203. this.area.off( "mousedown" );
  204. this.area.off( "mouseup" );
  205. this.area.off( "contextmenu" );
  206. this.area.off( "dblclick" );
  207. $(window).off( "keydown");
  208. $(window).off( "keyup");
  209. this.area[0].onmousewheel = undefined;
  210. APP.UI.messageHandler.openMessageDialog(
  211. "dialog.remoteControlTitle",
  212. "dialog.remoteControlStopMessage"
  213. );
  214. }
  215. /**
  216. * Calls this._stop() and sends stop message to the controlled participant.
  217. */
  218. stop() {
  219. if(!this.controlledParticipant) {
  220. return;
  221. }
  222. this._sendRemoteControlEvent(this.controlledParticipant, {
  223. type: EVENT_TYPES.stop
  224. });
  225. this._stop();
  226. }
  227. /**
  228. * Handler for mouse click events.
  229. * @param {String} type the type of event ("mousedown"/"mouseup")
  230. * @param {Event} event the mouse event.
  231. */
  232. _onMouseClickHandler(type, event) {
  233. this._sendRemoteControlEvent(this.controlledParticipant, {
  234. type: type,
  235. button: event.which
  236. });
  237. }
  238. /**
  239. * Returns true if the remote control session is started.
  240. * @returns {boolean}
  241. */
  242. isStarted() {
  243. return this.controlledParticipant !== null;
  244. }
  245. /**
  246. * Returns the id of the requested participant
  247. * @returns {string} this.requestedParticipant
  248. */
  249. getRequestedParticipant() {
  250. return this.requestedParticipant;
  251. }
  252. /**
  253. * Handler for key press events.
  254. * @param {String} type the type of event ("keydown"/"keyup")
  255. * @param {Event} event the key event.
  256. */
  257. _onKeyPessHandler(type, event) {
  258. this._sendRemoteControlEvent(this.controlledParticipant, {
  259. type: type,
  260. key: getKey(event),
  261. modifiers: getModifiers(event),
  262. });
  263. }
  264. /**
  265. * Calls the stop method if the other side have left.
  266. * @param {string} id - the user id for the participant that have left
  267. */
  268. _onUserLeft(id) {
  269. if(this.controlledParticipant === id) {
  270. this._stop();
  271. }
  272. }
  273. }