You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  1. // @flow
  2. import * as JitsiMeetConferenceEvents from '../../ConferenceEvents';
  3. import { parseJWTFromURLParams } from '../../react/features/base/jwt';
  4. import { sendAnalyticsEvent } from '../../react/features/analytics';
  5. import { getJitsiMeetTransport } from '../transport';
  6. import { API_ID } from './constants';
  7. const logger = require('jitsi-meet-logger').getLogger(__filename);
  8. declare var APP: Object;
  9. /**
  10. * List of the available commands.
  11. */
  12. let commands = {};
  13. /**
  14. * The state of screen sharing(started/stopped) before the screen sharing is
  15. * enabled and initialized.
  16. * NOTE: This flag help us to cache the state and use it if toggle-share-screen
  17. * was received before the initialization.
  18. */
  19. let initialScreenSharingState = false;
  20. /**
  21. * The transport instance used for communication with external apps.
  22. *
  23. * @type {Transport}
  24. */
  25. const transport = getJitsiMeetTransport();
  26. /**
  27. * The current audio availability.
  28. *
  29. * @type {boolean}
  30. */
  31. let audioAvailable = true;
  32. /**
  33. * The current video availability.
  34. *
  35. * @type {boolean}
  36. */
  37. let videoAvailable = true;
  38. /**
  39. * Initializes supported commands.
  40. *
  41. * @returns {void}
  42. */
  43. function initCommands() {
  44. commands = {
  45. 'display-name':
  46. APP.conference.changeLocalDisplayName.bind(APP.conference),
  47. 'toggle-audio': () => {
  48. sendAnalyticsEvent('api.toggle.audio');
  49. logger.log('Audio toggle: API command received');
  50. APP.conference.toggleAudioMuted(false /* no UI */);
  51. },
  52. 'toggle-video': () => {
  53. sendAnalyticsEvent('api.toggle.video');
  54. logger.log('Video toggle: API command received');
  55. APP.conference.toggleVideoMuted(false /* no UI */);
  56. },
  57. 'toggle-film-strip': APP.UI.toggleFilmstrip,
  58. 'toggle-chat': APP.UI.toggleChat,
  59. 'toggle-contact-list': APP.UI.toggleContactList,
  60. 'toggle-share-screen': toggleScreenSharing,
  61. 'video-hangup': () => APP.conference.hangup(true),
  62. 'email': APP.conference.changeLocalEmail,
  63. 'avatar-url': APP.conference.changeLocalAvatarUrl
  64. };
  65. transport.on('event', ({ data, name }) => {
  66. if (name && commands[name]) {
  67. commands[name](...data);
  68. return true;
  69. }
  70. return false;
  71. });
  72. transport.on('request', ({ name }, callback) => {
  73. switch (name) {
  74. case 'is-audio-muted':
  75. callback(APP.conference.isLocalAudioMuted());
  76. break;
  77. case 'is-video-muted':
  78. callback(APP.conference.isLocalVideoMuted());
  79. break;
  80. case 'is-audio-available':
  81. callback(audioAvailable);
  82. break;
  83. case 'is-video-available':
  84. callback(videoAvailable);
  85. break;
  86. default:
  87. return false;
  88. }
  89. return true;
  90. });
  91. }
  92. /**
  93. * Listens for desktop/screen sharing enabled events and toggles the screen
  94. * sharing if needed.
  95. *
  96. * @param {boolean} enabled - Current screen sharing enabled status.
  97. * @returns {void}
  98. */
  99. function onDesktopSharingEnabledChanged(enabled = false) {
  100. if (enabled && initialScreenSharingState) {
  101. toggleScreenSharing();
  102. }
  103. }
  104. /**
  105. * Check whether the API should be enabled or not.
  106. *
  107. * @returns {boolean}
  108. */
  109. function shouldBeEnabled() {
  110. return (
  111. typeof API_ID === 'number'
  112. // XXX Enable the API when a JSON Web Token (JWT) is specified in
  113. // the location/URL because then it is very likely that the Jitsi
  114. // Meet (Web) app is being used by an external/wrapping (Web) app
  115. // and, consequently, the latter will need to communicate with the
  116. // former. (The described logic is merely a heuristic though.)
  117. || parseJWTFromURLParams());
  118. }
  119. /**
  120. * Executes on toggle-share-screen command.
  121. *
  122. * @returns {void}
  123. */
  124. function toggleScreenSharing() {
  125. if (APP.conference.isDesktopSharingEnabled) {
  126. // eslint-disable-next-line no-empty-function
  127. APP.conference.toggleScreenSharing().catch(() => {});
  128. } else {
  129. initialScreenSharingState = !initialScreenSharingState;
  130. }
  131. }
  132. /**
  133. * Implements API class that communicates with external API class and provides
  134. * interface to access Jitsi Meet features by external applications that embed
  135. * Jitsi Meet.
  136. */
  137. class API {
  138. _enabled: boolean;
  139. /**
  140. * Initializes the API. Setups message event listeners that will receive
  141. * information from external applications that embed Jitsi Meet. It also
  142. * sends a message to the external application that API is initialized.
  143. *
  144. * @param {Object} options - Optional parameters.
  145. * @returns {void}
  146. */
  147. init() {
  148. if (!shouldBeEnabled()) {
  149. return;
  150. }
  151. /**
  152. * Current status (enabled/disabled) of API.
  153. *
  154. * @private
  155. * @type {boolean}
  156. */
  157. this._enabled = true;
  158. APP.conference.addListener(
  159. JitsiMeetConferenceEvents.DESKTOP_SHARING_ENABLED_CHANGED,
  160. onDesktopSharingEnabledChanged);
  161. initCommands();
  162. }
  163. /**
  164. * Notify external application (if API is enabled) that the large video
  165. * visibility changed.
  166. *
  167. * @param {boolean} isHidden - True if the large video is hidden and false
  168. * otherwise.
  169. * @returns {void}
  170. */
  171. notifyLargeVideoVisibilityChanged(isHidden: boolean) {
  172. this._sendEvent({
  173. name: 'large-video-visibility-changed',
  174. isVisible: !isHidden
  175. });
  176. }
  177. /**
  178. * Sends event to the external application.
  179. *
  180. * @param {Object} event - The event to be sent.
  181. * @returns {void}
  182. */
  183. _sendEvent(event: Object = {}) {
  184. if (this._enabled) {
  185. transport.sendEvent(event);
  186. }
  187. }
  188. /**
  189. * Notify external application (if API is enabled) that message was sent.
  190. *
  191. * @param {string} message - Message body.
  192. * @returns {void}
  193. */
  194. notifySendingChatMessage(message: string) {
  195. this._sendEvent({
  196. name: 'outgoing-message',
  197. message
  198. });
  199. }
  200. /**
  201. * Notify external application (if API is enabled) that message was
  202. * received.
  203. *
  204. * @param {Object} options - Object with the message properties.
  205. * @returns {void}
  206. */
  207. notifyReceivedChatMessage(
  208. { body, id, nick, ts }: {
  209. body: *, id: string, nick: string, ts: *
  210. } = {}) {
  211. if (APP.conference.isLocalId(id)) {
  212. return;
  213. }
  214. this._sendEvent({
  215. name: 'incoming-message',
  216. from: id,
  217. message: body,
  218. nick,
  219. stamp: ts
  220. });
  221. }
  222. /**
  223. * Notify external application (if API is enabled) that user joined the
  224. * conference.
  225. *
  226. * @param {string} id - User id.
  227. * @param {Object} props - The display name of the user.
  228. * @returns {void}
  229. */
  230. notifyUserJoined(id: string, props: Object) {
  231. this._sendEvent({
  232. name: 'participant-joined',
  233. id,
  234. ...props
  235. });
  236. }
  237. /**
  238. * Notify external application (if API is enabled) that user left the
  239. * conference.
  240. *
  241. * @param {string} id - User id.
  242. * @returns {void}
  243. */
  244. notifyUserLeft(id: string) {
  245. this._sendEvent({
  246. name: 'participant-left',
  247. id
  248. });
  249. }
  250. /**
  251. * Notify external application (if API is enabled) that user changed their
  252. * avatar.
  253. *
  254. * @param {string} id - User id.
  255. * @param {string} avatarURL - The new avatar URL of the participant.
  256. * @returns {void}
  257. */
  258. notifyAvatarChanged(id: string, avatarURL: string) {
  259. this._sendEvent({
  260. name: 'avatar-changed',
  261. avatarURL,
  262. id
  263. });
  264. }
  265. /**
  266. * Notify external application (if API is enabled) that user changed their
  267. * nickname.
  268. *
  269. * @param {string} id - User id.
  270. * @param {string} displayname - User nickname.
  271. * @param {string} formattedDisplayName - The display name shown in Jitsi
  272. * meet's UI for the user.
  273. * @returns {void}
  274. */
  275. notifyDisplayNameChanged(
  276. id: string,
  277. { displayName, formattedDisplayName }: Object) {
  278. this._sendEvent({
  279. name: 'display-name-change',
  280. displayname: displayName,
  281. formattedDisplayName,
  282. id
  283. });
  284. }
  285. /**
  286. * Notify external application (if API is enabled) that the conference has
  287. * been joined.
  288. *
  289. * @param {string} roomName - The room name.
  290. * @param {string} id - The id of the local user.
  291. * @param {Object} props - The display name and avatar URL of the local
  292. * user.
  293. * @returns {void}
  294. */
  295. notifyConferenceJoined(roomName: string, id: string, props: Object) {
  296. this._sendEvent({
  297. name: 'video-conference-joined',
  298. roomName,
  299. id,
  300. ...props
  301. });
  302. }
  303. /**
  304. * Notify external application (if API is enabled) that user changed their
  305. * nickname.
  306. *
  307. * @param {string} roomName - User id.
  308. * @returns {void}
  309. */
  310. notifyConferenceLeft(roomName: string) {
  311. this._sendEvent({
  312. name: 'video-conference-left',
  313. roomName
  314. });
  315. }
  316. /**
  317. * Notify external application (if API is enabled) that we are ready to be
  318. * closed.
  319. *
  320. * @returns {void}
  321. */
  322. notifyReadyToClose() {
  323. this._sendEvent({ name: 'video-ready-to-close' });
  324. }
  325. /**
  326. * Notify external application (if API is enabled) for audio muted status
  327. * changed.
  328. *
  329. * @param {boolean} muted - The new muted status.
  330. * @returns {void}
  331. */
  332. notifyAudioMutedStatusChanged(muted: boolean) {
  333. this._sendEvent({
  334. name: 'audio-mute-status-changed',
  335. muted
  336. });
  337. }
  338. /**
  339. * Notify external application (if API is enabled) for video muted status
  340. * changed.
  341. *
  342. * @param {boolean} muted - The new muted status.
  343. * @returns {void}
  344. */
  345. notifyVideoMutedStatusChanged(muted: boolean) {
  346. this._sendEvent({
  347. name: 'video-mute-status-changed',
  348. muted
  349. });
  350. }
  351. /**
  352. * Notify external application (if API is enabled) for audio availability
  353. * changed.
  354. *
  355. * @param {boolean} available - True if available and false otherwise.
  356. * @returns {void}
  357. */
  358. notifyAudioAvailabilityChanged(available: boolean) {
  359. audioAvailable = available;
  360. this._sendEvent({
  361. name: 'audio-availability-changed',
  362. available
  363. });
  364. }
  365. /**
  366. * Notify external application (if API is enabled) for video available
  367. * status changed.
  368. *
  369. * @param {boolean} available - True if available and false otherwise.
  370. * @returns {void}
  371. */
  372. notifyVideoAvailabilityChanged(available: boolean) {
  373. videoAvailable = available;
  374. this._sendEvent({
  375. name: 'video-availability-changed',
  376. available
  377. });
  378. }
  379. /**
  380. * Notify external application (if API is enabled) that the on stage
  381. * participant has changed.
  382. *
  383. * @param {string} id - User id of the new on stage participant.
  384. * @returns {void}
  385. */
  386. notifyOnStageParticipantChanged(id: string) {
  387. this._sendEvent({
  388. name: 'on-stage-participant-changed',
  389. id
  390. });
  391. }
  392. /**
  393. * Disposes the allocated resources.
  394. *
  395. * @returns {void}
  396. */
  397. dispose() {
  398. if (this._enabled) {
  399. this._enabled = false;
  400. APP.conference.removeListener(
  401. JitsiMeetConferenceEvents.DESKTOP_SHARING_ENABLED_CHANGED,
  402. onDesktopSharingEnabledChanged);
  403. }
  404. }
  405. }
  406. export default new API();