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.

API.js 12KB

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