您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

Controller.js 12KB

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