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

Controller.js 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359
  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. if(!this.enabled) {
  161. return;
  162. }
  163. APP.UI.addListener(UIEvents.LARGE_VIDEO_ID_CHANGED,
  164. this._largeVideoChangedListener);
  165. APP.conference.addConferenceListener(
  166. ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
  167. this._stopListener);
  168. APP.conference.addConferenceListener(ConferenceEvents.USER_LEFT,
  169. this._userLeftListener);
  170. this.resume();
  171. }
  172. /**
  173. * Disables the keyboatd shortcuts. Starts collecting remote control
  174. * events.
  175. *
  176. * It can be used to resume an active remote control session wchich was
  177. * paused with this.pause().
  178. */
  179. resume() {
  180. if(!this.enabled || this.isCollectingEvents) {
  181. return;
  182. }
  183. this.isCollectingEvents = true;
  184. APP.keyboardshortcut.enable(false);
  185. this.area = $("#largeVideoWrapper");
  186. this.area.mousemove(event => {
  187. const position = this.area.position();
  188. this._sendRemoteControlEvent(this.controlledParticipant, {
  189. type: EVENT_TYPES.mousemove,
  190. x: (event.pageX - position.left)/this.area.width(),
  191. y: (event.pageY - position.top)/this.area.height()
  192. });
  193. });
  194. this.area.mousedown(this._onMouseClickHandler.bind(this,
  195. EVENT_TYPES.mousedown));
  196. this.area.mouseup(this._onMouseClickHandler.bind(this,
  197. EVENT_TYPES.mouseup));
  198. this.area.dblclick(
  199. this._onMouseClickHandler.bind(this, EVENT_TYPES.mousedblclick));
  200. this.area.contextmenu(() => false);
  201. this.area[0].onmousewheel = event => {
  202. this._sendRemoteControlEvent(this.controlledParticipant, {
  203. type: EVENT_TYPES.mousescroll,
  204. x: event.deltaX,
  205. y: event.deltaY
  206. });
  207. };
  208. $(window).keydown(this._onKeyPessHandler.bind(this,
  209. EVENT_TYPES.keydown));
  210. $(window).keyup(this._onKeyPessHandler.bind(this, EVENT_TYPES.keyup));
  211. }
  212. /**
  213. * Stops processing the mouse and keyboard events. Removes added listeners.
  214. * Enables the keyboard shortcuts. Displays dialog to notify the user that
  215. * remote control session has ended.
  216. */
  217. _stop() {
  218. if(!this.controlledParticipant) {
  219. return;
  220. }
  221. APP.UI.removeListener(UIEvents.LARGE_VIDEO_ID_CHANGED,
  222. this._largeVideoChangedListener);
  223. APP.conference.removeConferenceListener(
  224. ConferenceEvents.ENDPOINT_MESSAGE_RECEIVED,
  225. this._stopListener);
  226. APP.conference.removeConferenceListener(ConferenceEvents.USER_LEFT,
  227. this._userLeftListener);
  228. this.controlledParticipant = null;
  229. this.pause();
  230. APP.UI.messageHandler.openMessageDialog(
  231. "dialog.remoteControlTitle",
  232. "dialog.remoteControlStopMessage"
  233. );
  234. }
  235. /**
  236. * Executes this._stop() mehtod:
  237. * Stops processing the mouse and keyboard events. Removes added listeners.
  238. * Enables the keyboard shortcuts. Displays dialog to notify the user that
  239. * remote control session has ended.
  240. *
  241. * In addition:
  242. * Sends stop message to the controlled participant.
  243. */
  244. stop() {
  245. if(!this.controlledParticipant) {
  246. return;
  247. }
  248. this._sendRemoteControlEvent(this.controlledParticipant, {
  249. type: EVENT_TYPES.stop
  250. });
  251. this._stop();
  252. }
  253. /**
  254. * Pauses the collecting of events and enables the keyboard shortcus. But
  255. * it doesn't removes any other listeners. Basically the remote control
  256. * session will be still active after this.pause(), but no events from the
  257. * controller side will be captured and sent.
  258. *
  259. * You can resume the collecting of the events with this.resume().
  260. */
  261. pause() {
  262. if(!this.controlledParticipant) {
  263. return;
  264. }
  265. this.isCollectingEvents = false;
  266. APP.keyboardshortcut.enable(true);
  267. this.area.off( "mousemove" );
  268. this.area.off( "mousedown" );
  269. this.area.off( "mouseup" );
  270. this.area.off( "contextmenu" );
  271. this.area.off( "dblclick" );
  272. $(window).off( "keydown");
  273. $(window).off( "keyup");
  274. this.area[0].onmousewheel = undefined;
  275. }
  276. /**
  277. * Handler for mouse click events.
  278. * @param {String} type the type of event ("mousedown"/"mouseup")
  279. * @param {Event} event the mouse event.
  280. */
  281. _onMouseClickHandler(type, event) {
  282. this._sendRemoteControlEvent(this.controlledParticipant, {
  283. type: type,
  284. button: event.which
  285. });
  286. }
  287. /**
  288. * Returns true if the remote control session is started.
  289. * @returns {boolean}
  290. */
  291. isStarted() {
  292. return this.controlledParticipant !== null;
  293. }
  294. /**
  295. * Returns the id of the requested participant
  296. * @returns {string} this.requestedParticipant
  297. */
  298. getRequestedParticipant() {
  299. return this.requestedParticipant;
  300. }
  301. /**
  302. * Handler for key press events.
  303. * @param {String} type the type of event ("keydown"/"keyup")
  304. * @param {Event} event the key event.
  305. */
  306. _onKeyPessHandler(type, event) {
  307. this._sendRemoteControlEvent(this.controlledParticipant, {
  308. type: type,
  309. key: getKey(event),
  310. modifiers: getModifiers(event),
  311. });
  312. }
  313. /**
  314. * Calls the stop method if the other side have left.
  315. * @param {string} id - the user id for the participant that have left
  316. */
  317. _onUserLeft(id) {
  318. if(this.controlledParticipant === id) {
  319. this._stop();
  320. }
  321. }
  322. /**
  323. * Handles changes of the participant displayed on the large video.
  324. * @param {string} id - the user id for the participant that is displayed.
  325. */
  326. _onLargeVideoIdChanged(id) {
  327. if (!this.controlledParticipant) {
  328. return;
  329. }
  330. if(this.controlledParticipant == id) {
  331. this.resume();
  332. } else {
  333. this.pause();
  334. }
  335. }
  336. }