Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

Controller.js 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  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. import UIEvents from "../../service/UI/UIEvents";
  7. const ConferenceEvents = JitsiMeetJS.events.conference;
  8. /**
  9. * Extract the keyboard key from the keyboard event.
  10. * @param event {KeyboardEvent} the event.
  11. * @returns {KEYS} the key that is pressed or undefined.
  12. */
  13. function getKey(event) {
  14. return KeyCodes.keyboardEventToKey(event);
  15. }
  16. /**
  17. * Extract the modifiers from the keyboard event.
  18. * @param event {KeyboardEvent} the event.
  19. * @returns {Array} with possible values: "shift", "control", "alt", "command".
  20. */
  21. function getModifiers(event) {
  22. let modifiers = [];
  23. if(event.shiftKey) {
  24. modifiers.push("shift");
  25. }
  26. if(event.ctrlKey) {
  27. modifiers.push("control");
  28. }
  29. if(event.altKey) {
  30. modifiers.push("alt");
  31. }
  32. if(event.metaKey) {
  33. modifiers.push("command");
  34. }
  35. return modifiers;
  36. }
  37. /**
  38. * This class represents the controller party for a remote controller session.
  39. * It listens for mouse and keyboard events and sends them to the receiver
  40. * party of the remote control session.
  41. */
  42. export default class Controller extends RemoteControlParticipant {
  43. /**
  44. * Creates new instance.
  45. */
  46. constructor() {
  47. super();
  48. this.isCollectingEvents = false;
  49. this.controlledParticipant = null;
  50. this.requestedParticipant = null;
  51. this._stopListener = this._handleRemoteControlStoppedEvent.bind(this);
  52. this._userLeftListener = this._onUserLeft.bind(this);
  53. this._largeVideoChangedListener
  54. = this._onLargeVideoIdChanged.bind(this);
  55. }
  56. /**
  57. * Requests permissions from the remote control receiver side.
  58. * @param {string} userId the user id of the participant that will be
  59. * requested.
  60. * @returns {Promise<boolean>} - resolve values:
  61. * true - accept
  62. * false - deny
  63. * null - the participant has left.
  64. */
  65. requestPermissions(userId) {
  66. if(!this.enabled) {
  67. return Promise.reject(new Error("Remote control is disabled!"));
  68. }
  69. return new Promise((resolve, reject) => {
  70. const clearRequest = () => {
  71. this.requestedParticipant = null;
  72. APP.conference.removeConferenceListener(
  73. ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
  74. permissionsReplyListener);
  75. APP.conference.removeConferenceListener(
  76. ConferenceEvents.USER_LEFT,
  77. onUserLeft);
  78. };
  79. const permissionsReplyListener = (participant, event) => {
  80. let result = null;
  81. try {
  82. result = this._handleReply(participant, event);
  83. } catch (e) {
  84. reject(e);
  85. }
  86. if(result !== null) {
  87. clearRequest();
  88. resolve(result);
  89. }
  90. };
  91. const onUserLeft = (id) => {
  92. if(id === this.requestedParticipant) {
  93. clearRequest();
  94. resolve(null);
  95. }
  96. };
  97. APP.conference.addConferenceListener(
  98. ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
  99. permissionsReplyListener);
  100. APP.conference.addConferenceListener(ConferenceEvents.USER_LEFT,
  101. onUserLeft);
  102. this.requestedParticipant = userId;
  103. this._sendRemoteControlEvent(userId, {
  104. type: EVENT_TYPES.permissions,
  105. action: PERMISSIONS_ACTIONS.request
  106. }, e => {
  107. clearRequest();
  108. reject(e);
  109. });
  110. });
  111. }
  112. /**
  113. * Handles the reply of the permissions request.
  114. * @param {JitsiParticipant} participant the participant that has sent the
  115. * reply
  116. * @param {object} event the remote control event.
  117. */
  118. _handleReply(participant, event) {
  119. const remoteControlEvent = event.event;
  120. const userId = participant.getId();
  121. if(this.enabled && event.type === REMOTE_CONTROL_EVENT_TYPE
  122. && remoteControlEvent.type === EVENT_TYPES.permissions
  123. && userId === this.requestedParticipant) {
  124. switch(remoteControlEvent.action) {
  125. case PERMISSIONS_ACTIONS.grant: {
  126. this.controlledParticipant = userId;
  127. this._start();
  128. return true;
  129. }
  130. case PERMISSIONS_ACTIONS.deny:
  131. return false;
  132. case PERMISSIONS_ACTIONS.error:
  133. throw new Error("Error occurred on receiver side");
  134. default:
  135. throw new Error("Unknown reply received!");
  136. }
  137. } else {
  138. //different message type or another user -> ignoring the message
  139. return null;
  140. }
  141. }
  142. /**
  143. * Handles remote control stopped.
  144. * @param {JitsiParticipant} participant the participant that has sent the
  145. * event
  146. * @param {object} event the the remote control event.
  147. */
  148. _handleRemoteControlStoppedEvent(participant, event) {
  149. if(this.enabled && event.type === REMOTE_CONTROL_EVENT_TYPE
  150. && event.event.type === EVENT_TYPES.stop
  151. && participant.getId() === this.controlledParticipant) {
  152. this._stop();
  153. }
  154. }
  155. /**
  156. * Starts processing the mouse and keyboard events. Sets conference
  157. * listeners. Disables keyboard events.
  158. */
  159. _start() {
  160. APP.UI.addListener(UIEvents.LARGE_VIDEO_ID_CHANGED,
  161. this._largeVideoChangedListener);
  162. APP.conference.addConferenceListener(
  163. ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
  164. this._stopListener);
  165. APP.conference.addConferenceListener(ConferenceEvents.USER_LEFT,
  166. this._userLeftListener);
  167. this.resume();
  168. }
  169. /**
  170. * Disables the keyboatd shortcuts. Starts collecting remote control
  171. * events.
  172. *
  173. * It can be used to resume an active remote control session wchich was
  174. * paused with this.pause().
  175. */
  176. resume() {
  177. if(!this.enabled || this.isCollectingEvents) {
  178. return;
  179. }
  180. this.isCollectingEvents = true;
  181. APP.keyboardshortcut.enable(false);
  182. this.area = $("#largeVideoWrapper");
  183. this.area.mousemove(event => {
  184. const position = this.area.position();
  185. this._sendRemoteControlEvent(this.controlledParticipant, {
  186. type: EVENT_TYPES.mousemove,
  187. x: (event.pageX - position.left)/this.area.width(),
  188. y: (event.pageY - position.top)/this.area.height()
  189. });
  190. });
  191. this.area.mousedown(this._onMouseClickHandler.bind(this,
  192. EVENT_TYPES.mousedown));
  193. this.area.mouseup(this._onMouseClickHandler.bind(this,
  194. EVENT_TYPES.mouseup));
  195. this.area.dblclick(
  196. this._onMouseClickHandler.bind(this, EVENT_TYPES.mousedblclick));
  197. this.area.contextmenu(() => false);
  198. this.area[0].onmousewheel = event => {
  199. this._sendRemoteControlEvent(this.controlledParticipant, {
  200. type: EVENT_TYPES.mousescroll,
  201. x: event.deltaX,
  202. y: event.deltaY
  203. });
  204. };
  205. $(window).keydown(this._onKeyPessHandler.bind(this,
  206. EVENT_TYPES.keydown));
  207. $(window).keyup(this._onKeyPessHandler.bind(this, EVENT_TYPES.keyup));
  208. }
  209. /**
  210. * Stops processing the mouse and keyboard events. Removes added listeners.
  211. * Enables the keyboard shortcuts. Displays dialog to notify the user that
  212. * remote control session has ended.
  213. */
  214. _stop() {
  215. if(!this.controlledParticipant) {
  216. return;
  217. }
  218. APP.UI.removeListener(UIEvents.LARGE_VIDEO_ID_CHANGED,
  219. this._largeVideoChangedListener);
  220. APP.conference.removeConferenceListener(
  221. ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
  222. this._stopListener);
  223. APP.conference.removeConferenceListener(ConferenceEvents.USER_LEFT,
  224. this._userLeftListener);
  225. this.controlledParticipant = null;
  226. this.pause();
  227. APP.UI.messageHandler.openMessageDialog(
  228. "dialog.remoteControlTitle",
  229. "dialog.remoteControlStopMessage"
  230. );
  231. }
  232. /**
  233. * Executes this._stop() mehtod:
  234. * Stops processing the mouse and keyboard events. Removes added listeners.
  235. * Enables the keyboard shortcuts. Displays dialog to notify the user that
  236. * remote control session has ended.
  237. *
  238. * In addition:
  239. * Sends stop message to the controlled participant.
  240. */
  241. stop() {
  242. if(!this.controlledParticipant) {
  243. return;
  244. }
  245. this._sendRemoteControlEvent(this.controlledParticipant, {
  246. type: EVENT_TYPES.stop
  247. });
  248. this._stop();
  249. }
  250. /**
  251. * Pauses the collecting of events and enables the keyboard shortcus. But
  252. * it doesn't removes any other listeners. Basically the remote control
  253. * session will be still active after this.pause(), but no events from the
  254. * controller side will be captured and sent.
  255. *
  256. * You can resume the collecting of the events with this.resume().
  257. */
  258. pause() {
  259. if(!this.controlledParticipant) {
  260. return;
  261. }
  262. this.isCollectingEvents = false;
  263. APP.keyboardshortcut.enable(true);
  264. this.area.off( "mousemove" );
  265. this.area.off( "mousedown" );
  266. this.area.off( "mouseup" );
  267. this.area.off( "contextmenu" );
  268. this.area.off( "dblclick" );
  269. $(window).off( "keydown");
  270. $(window).off( "keyup");
  271. this.area[0].onmousewheel = undefined;
  272. }
  273. /**
  274. * Handler for mouse click events.
  275. * @param {String} type the type of event ("mousedown"/"mouseup")
  276. * @param {Event} event the mouse event.
  277. */
  278. _onMouseClickHandler(type, event) {
  279. this._sendRemoteControlEvent(this.controlledParticipant, {
  280. type: type,
  281. button: event.which
  282. });
  283. }
  284. /**
  285. * Returns true if the remote control session is started.
  286. * @returns {boolean}
  287. */
  288. isStarted() {
  289. return this.controlledParticipant !== null;
  290. }
  291. /**
  292. * Returns the id of the requested participant
  293. * @returns {string} this.requestedParticipant
  294. */
  295. getRequestedParticipant() {
  296. return this.requestedParticipant;
  297. }
  298. /**
  299. * Handler for key press events.
  300. * @param {String} type the type of event ("keydown"/"keyup")
  301. * @param {Event} event the key event.
  302. */
  303. _onKeyPessHandler(type, event) {
  304. this._sendRemoteControlEvent(this.controlledParticipant, {
  305. type: type,
  306. key: getKey(event),
  307. modifiers: getModifiers(event),
  308. });
  309. }
  310. /**
  311. * Calls the stop method if the other side have left.
  312. * @param {string} id - the user id for the participant that have left
  313. */
  314. _onUserLeft(id) {
  315. if(this.controlledParticipant === id) {
  316. this._stop();
  317. }
  318. }
  319. /**
  320. * Handles changes of the participant displayed on the large video.
  321. * @param {string} id - the user id for the participant that is displayed.
  322. */
  323. _onLargeVideoIdChanged(id) {
  324. if (!this.controlledParticipant) {
  325. return;
  326. }
  327. if(this.controlledParticipant == id) {
  328. this.resume();
  329. } else {
  330. this.pause();
  331. }
  332. }
  333. }