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

Controller.js 13KB

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