選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

Controller.js 13KB

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