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 31KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038
  1. // @flow
  2. import Logger from 'jitsi-meet-logger';
  3. import * as JitsiMeetConferenceEvents from '../../ConferenceEvents';
  4. import {
  5. createApiEvent,
  6. sendAnalytics
  7. } from '../../react/features/analytics';
  8. import {
  9. getCurrentConference,
  10. sendTones,
  11. setPassword,
  12. setSubject
  13. } from '../../react/features/base/conference';
  14. import { parseJWTFromURLParams } from '../../react/features/base/jwt';
  15. import { JitsiRecordingConstants } from '../../react/features/base/lib-jitsi-meet';
  16. import {
  17. processExternalDeviceRequest
  18. } from '../../react/features/device-selection/functions';
  19. import { isEnabled as isDropboxEnabled } from '../../react/features/dropbox';
  20. import { toggleE2EE } from '../../react/features/e2ee/actions';
  21. import { invite } from '../../react/features/invite';
  22. import {
  23. captureLargeVideoScreenshot,
  24. resizeLargeVideo,
  25. selectParticipantInLargeVideo
  26. } from '../../react/features/large-video/actions';
  27. import { toggleLobbyMode } from '../../react/features/lobby/actions.web';
  28. import { RECORDING_TYPES } from '../../react/features/recording/constants';
  29. import { getActiveSession } from '../../react/features/recording/functions';
  30. import { muteAllParticipants } from '../../react/features/remote-video-menu/actions';
  31. import { toggleTileView } from '../../react/features/video-layout';
  32. import { setVideoQuality } from '../../react/features/video-quality';
  33. import { getJitsiMeetTransport } from '../transport';
  34. import { API_ID, ENDPOINT_TEXT_MESSAGE_NAME } from './constants';
  35. const logger = Logger.getLogger(__filename);
  36. declare var APP: Object;
  37. /**
  38. * List of the available commands.
  39. */
  40. let commands = {};
  41. /**
  42. * The state of screen sharing(started/stopped) before the screen sharing is
  43. * enabled and initialized.
  44. * NOTE: This flag help us to cache the state and use it if toggle-share-screen
  45. * was received before the initialization.
  46. */
  47. let initialScreenSharingState = false;
  48. /**
  49. * The transport instance used for communication with external apps.
  50. *
  51. * @type {Transport}
  52. */
  53. const transport = getJitsiMeetTransport();
  54. /**
  55. * The current audio availability.
  56. *
  57. * @type {boolean}
  58. */
  59. let audioAvailable = true;
  60. /**
  61. * The current video availability.
  62. *
  63. * @type {boolean}
  64. */
  65. let videoAvailable = true;
  66. /**
  67. * Initializes supported commands.
  68. *
  69. * @returns {void}
  70. */
  71. function initCommands() {
  72. commands = {
  73. 'display-name': displayName => {
  74. sendAnalytics(createApiEvent('display.name.changed'));
  75. APP.conference.changeLocalDisplayName(displayName);
  76. },
  77. 'mute-everyone': () => {
  78. sendAnalytics(createApiEvent('muted-everyone'));
  79. const participants = APP.store.getState()['features/base/participants'];
  80. const localIds = participants
  81. .filter(participant => participant.local)
  82. .filter(participant => participant.role === 'moderator')
  83. .map(participant => participant.id);
  84. APP.store.dispatch(muteAllParticipants(localIds));
  85. },
  86. 'toggle-lobby': isLobbyEnabled => {
  87. APP.store.dispatch(toggleLobbyMode(isLobbyEnabled));
  88. },
  89. 'password': password => {
  90. const { conference, passwordRequired }
  91. = APP.store.getState()['features/base/conference'];
  92. if (passwordRequired) {
  93. sendAnalytics(createApiEvent('submit.password'));
  94. APP.store.dispatch(setPassword(
  95. passwordRequired,
  96. passwordRequired.join,
  97. password
  98. ));
  99. } else {
  100. sendAnalytics(createApiEvent('password.changed'));
  101. APP.store.dispatch(setPassword(
  102. conference,
  103. conference.lock,
  104. password
  105. ));
  106. }
  107. },
  108. 'proxy-connection-event': event => {
  109. APP.conference.onProxyConnectionEvent(event);
  110. },
  111. 'resize-large-video': (width, height) => {
  112. logger.debug('Resize large video command received');
  113. sendAnalytics(createApiEvent('largevideo.resized'));
  114. APP.store.dispatch(resizeLargeVideo(width, height));
  115. },
  116. 'send-tones': (options = {}) => {
  117. const { duration, tones, pause } = options;
  118. APP.store.dispatch(sendTones(tones, duration, pause));
  119. },
  120. 'set-large-video-participant': participantId => {
  121. logger.debug('Set large video participant command received');
  122. sendAnalytics(createApiEvent('largevideo.participant.set'));
  123. APP.store.dispatch(selectParticipantInLargeVideo(participantId));
  124. },
  125. 'subject': subject => {
  126. sendAnalytics(createApiEvent('subject.changed'));
  127. APP.store.dispatch(setSubject(subject));
  128. },
  129. 'submit-feedback': feedback => {
  130. sendAnalytics(createApiEvent('submit.feedback'));
  131. APP.conference.submitFeedback(feedback.score, feedback.message);
  132. },
  133. 'toggle-audio': () => {
  134. sendAnalytics(createApiEvent('toggle-audio'));
  135. logger.log('Audio toggle: API command received');
  136. APP.conference.toggleAudioMuted(false /* no UI */);
  137. },
  138. 'toggle-video': () => {
  139. sendAnalytics(createApiEvent('toggle-video'));
  140. logger.log('Video toggle: API command received');
  141. APP.conference.toggleVideoMuted(false /* no UI */);
  142. },
  143. 'toggle-film-strip': () => {
  144. sendAnalytics(createApiEvent('film.strip.toggled'));
  145. APP.UI.toggleFilmstrip();
  146. },
  147. 'toggle-chat': () => {
  148. sendAnalytics(createApiEvent('chat.toggled'));
  149. APP.UI.toggleChat();
  150. },
  151. /**
  152. * Callback to invoke when the "toggle-share-screen" command is received.
  153. *
  154. * @param {Object} options - Additional details of how to perform
  155. * the action. Note this parameter is undocumented and experimental.
  156. * @param {boolean} options.enable - Whether trying to enable screen
  157. * sharing or to turn it off.
  158. * @returns {void}
  159. */
  160. 'toggle-share-screen': (options = {}) => {
  161. sendAnalytics(createApiEvent('screen.sharing.toggled'));
  162. toggleScreenSharing(options.enable);
  163. },
  164. 'toggle-tile-view': () => {
  165. sendAnalytics(createApiEvent('tile-view.toggled'));
  166. APP.store.dispatch(toggleTileView());
  167. },
  168. 'video-hangup': (showFeedbackDialog = true) => {
  169. sendAnalytics(createApiEvent('video.hangup'));
  170. APP.conference.hangup(showFeedbackDialog);
  171. },
  172. 'email': email => {
  173. sendAnalytics(createApiEvent('email.changed'));
  174. APP.conference.changeLocalEmail(email);
  175. },
  176. 'avatar-url': avatarUrl => {
  177. sendAnalytics(createApiEvent('avatar.url.changed'));
  178. APP.conference.changeLocalAvatarUrl(avatarUrl);
  179. },
  180. 'send-endpoint-text-message': (to, text) => {
  181. logger.debug('Send endpoint message command received');
  182. try {
  183. APP.conference.sendEndpointMessage(to, {
  184. name: ENDPOINT_TEXT_MESSAGE_NAME,
  185. text
  186. });
  187. } catch (err) {
  188. logger.error('Failed sending endpoint text message', err);
  189. }
  190. },
  191. 'toggle-e2ee': enabled => {
  192. logger.debug('Toggle E2EE key command received');
  193. APP.store.dispatch(toggleE2EE(enabled));
  194. },
  195. 'set-video-quality': frameHeight => {
  196. logger.debug('Set video quality command received');
  197. sendAnalytics(createApiEvent('set.video.quality'));
  198. APP.store.dispatch(setVideoQuality(frameHeight));
  199. },
  200. /**
  201. * Starts a file recording or streaming depending on the passed on params.
  202. * For youtube streams, `youtubeStreamKey` must be passed on. `youtubeBroadcastID` is optional.
  203. * For dropbox recording, recording `mode` should be `file` and a dropbox oauth2 token must be provided.
  204. * For file recording, recording `mode` should be `file` and optionally `shouldShare` could be passed on.
  205. * No other params should be passed.
  206. *
  207. * @param { string } arg.mode - Recording mode, either `file` or `stream`.
  208. * @param { string } arg.dropboxToken - Dropbox oauth2 token.
  209. * @param { boolean } arg.shouldShare - Whether the recording should be shared with the participants or not.
  210. * Only applies to certain jitsi meet deploys.
  211. * @param { string } arg.youtubeStreamKey - The youtube stream key.
  212. * @param { string } arg.youtubeBroadcastID - The youtube broacast ID.
  213. * @returns {void}
  214. */
  215. 'start-recording': ({ mode, dropboxToken, shouldShare, youtubeStreamKey, youtubeBroadcastID }) => {
  216. const state = APP.store.getState();
  217. const conference = getCurrentConference(state);
  218. if (!conference) {
  219. logger.error('Conference is not defined');
  220. return;
  221. }
  222. if (dropboxToken && !isDropboxEnabled(state)) {
  223. logger.error('Failed starting recording: dropbox is not enabled on this deployment');
  224. return;
  225. }
  226. if (mode === JitsiRecordingConstants.mode.STREAM && !youtubeStreamKey) {
  227. logger.error('Failed starting recording: missing youtube stream key');
  228. return;
  229. }
  230. let recordingConfig;
  231. if (mode === JitsiRecordingConstants.mode.FILE) {
  232. if (dropboxToken) {
  233. recordingConfig = {
  234. mode: JitsiRecordingConstants.mode.FILE,
  235. appData: JSON.stringify({
  236. 'file_recording_metadata': {
  237. 'upload_credentials': {
  238. 'service_name': RECORDING_TYPES.DROPBOX,
  239. 'token': dropboxToken
  240. }
  241. }
  242. })
  243. };
  244. } else {
  245. recordingConfig = {
  246. mode: JitsiRecordingConstants.mode.FILE,
  247. appData: JSON.stringify({
  248. 'file_recording_metadata': {
  249. 'share': shouldShare
  250. }
  251. })
  252. };
  253. }
  254. } else if (mode === JitsiRecordingConstants.mode.STREAM) {
  255. recordingConfig = {
  256. broadcastId: youtubeBroadcastID,
  257. mode: JitsiRecordingConstants.mode.STREAM,
  258. streamId: youtubeStreamKey
  259. };
  260. } else {
  261. logger.error('Invalid recording mode provided');
  262. return;
  263. }
  264. conference.startRecording(recordingConfig);
  265. },
  266. /**
  267. * Stops a recording or streaming in progress.
  268. *
  269. * @param {string} mode - `file` or `stream`.
  270. * @returns {void}
  271. */
  272. 'stop-recording': mode => {
  273. const state = APP.store.getState();
  274. const conference = getCurrentConference(state);
  275. if (!conference) {
  276. logger.error('Conference is not defined');
  277. return;
  278. }
  279. if (![ JitsiRecordingConstants.mode.FILE, JitsiRecordingConstants.mode.STREAM ].includes(mode)) {
  280. logger.error('Invalid recording mode provided!');
  281. return;
  282. }
  283. const activeSession = getActiveSession(state, mode);
  284. if (activeSession && activeSession.id) {
  285. conference.stopRecording(activeSession.id);
  286. } else {
  287. logger.error('No recording or streaming session found');
  288. }
  289. }
  290. };
  291. transport.on('event', ({ data, name }) => {
  292. if (name && commands[name]) {
  293. commands[name](...data);
  294. return true;
  295. }
  296. return false;
  297. });
  298. transport.on('request', (request, callback) => {
  299. const { dispatch, getState } = APP.store;
  300. if (processExternalDeviceRequest(dispatch, getState, request, callback)) {
  301. return true;
  302. }
  303. const { name } = request;
  304. switch (name) {
  305. case 'capture-largevideo-screenshot' :
  306. APP.store.dispatch(captureLargeVideoScreenshot())
  307. .then(dataURL => {
  308. let error;
  309. if (!dataURL) {
  310. error = new Error('No large video found!');
  311. }
  312. callback({
  313. error,
  314. dataURL
  315. });
  316. });
  317. break;
  318. case 'invite': {
  319. const { invitees } = request;
  320. if (!Array.isArray(invitees) || invitees.length === 0) {
  321. callback({
  322. error: new Error('Unexpected format of invitees')
  323. });
  324. break;
  325. }
  326. // The store should be already available because API.init is called
  327. // on appWillMount action.
  328. APP.store.dispatch(
  329. invite(invitees, true))
  330. .then(failedInvitees => {
  331. let error;
  332. let result;
  333. if (failedInvitees.length) {
  334. error = new Error('One or more invites failed!');
  335. } else {
  336. result = true;
  337. }
  338. callback({
  339. error,
  340. result
  341. });
  342. });
  343. break;
  344. }
  345. case 'is-audio-muted':
  346. callback(APP.conference.isLocalAudioMuted());
  347. break;
  348. case 'is-video-muted':
  349. callback(APP.conference.isLocalVideoMuted());
  350. break;
  351. case 'is-audio-available':
  352. callback(audioAvailable);
  353. break;
  354. case 'is-video-available':
  355. callback(videoAvailable);
  356. break;
  357. case 'is-sharing-screen':
  358. callback(Boolean(APP.conference.isSharingScreen));
  359. break;
  360. default:
  361. return false;
  362. }
  363. return true;
  364. });
  365. }
  366. /**
  367. * Listens for desktop/screen sharing enabled events and toggles the screen
  368. * sharing if needed.
  369. *
  370. * @param {boolean} enabled - Current screen sharing enabled status.
  371. * @returns {void}
  372. */
  373. function onDesktopSharingEnabledChanged(enabled = false) {
  374. if (enabled && initialScreenSharingState) {
  375. toggleScreenSharing();
  376. }
  377. }
  378. /**
  379. * Check whether the API should be enabled or not.
  380. *
  381. * @returns {boolean}
  382. */
  383. function shouldBeEnabled() {
  384. return (
  385. typeof API_ID === 'number'
  386. // XXX Enable the API when a JSON Web Token (JWT) is specified in
  387. // the location/URL because then it is very likely that the Jitsi
  388. // Meet (Web) app is being used by an external/wrapping (Web) app
  389. // and, consequently, the latter will need to communicate with the
  390. // former. (The described logic is merely a heuristic though.)
  391. || parseJWTFromURLParams());
  392. }
  393. /**
  394. * Executes on toggle-share-screen command.
  395. *
  396. * @param {boolean} [enable] - Whether this toggle is to explicitly enable or
  397. * disable screensharing. If not defined, the application will automatically
  398. * attempt to toggle between enabled and disabled. This boolean is useful for
  399. * explicitly setting desired screensharing state.
  400. * @returns {void}
  401. */
  402. function toggleScreenSharing(enable) {
  403. if (APP.conference.isDesktopSharingEnabled) {
  404. // eslint-disable-next-line no-empty-function
  405. APP.conference.toggleScreenSharing(enable).catch(() => {});
  406. } else {
  407. initialScreenSharingState = !initialScreenSharingState;
  408. }
  409. }
  410. /**
  411. * Implements API class that communicates with external API class and provides
  412. * interface to access Jitsi Meet features by external applications that embed
  413. * Jitsi Meet.
  414. */
  415. class API {
  416. _enabled: boolean;
  417. /**
  418. * Initializes the API. Setups message event listeners that will receive
  419. * information from external applications that embed Jitsi Meet. It also
  420. * sends a message to the external application that API is initialized.
  421. *
  422. * @param {Object} options - Optional parameters.
  423. * @returns {void}
  424. */
  425. init() {
  426. if (!shouldBeEnabled()) {
  427. return;
  428. }
  429. /**
  430. * Current status (enabled/disabled) of API.
  431. *
  432. * @private
  433. * @type {boolean}
  434. */
  435. this._enabled = true;
  436. APP.conference.addListener(
  437. JitsiMeetConferenceEvents.DESKTOP_SHARING_ENABLED_CHANGED,
  438. onDesktopSharingEnabledChanged);
  439. initCommands();
  440. }
  441. /**
  442. * Notify external application (if API is enabled) that the large video
  443. * visibility changed.
  444. *
  445. * @param {boolean} isHidden - True if the large video is hidden and false
  446. * otherwise.
  447. * @returns {void}
  448. */
  449. notifyLargeVideoVisibilityChanged(isHidden: boolean) {
  450. this._sendEvent({
  451. name: 'large-video-visibility-changed',
  452. isVisible: !isHidden
  453. });
  454. }
  455. /**
  456. * Notifies the external application (spot) that the local jitsi-participant
  457. * has a status update.
  458. *
  459. * @param {Object} event - The message to pass onto spot.
  460. * @returns {void}
  461. */
  462. sendProxyConnectionEvent(event: Object) {
  463. this._sendEvent({
  464. name: 'proxy-connection-event',
  465. ...event
  466. });
  467. }
  468. /**
  469. * Sends event to the external application.
  470. *
  471. * @param {Object} event - The event to be sent.
  472. * @returns {void}
  473. */
  474. _sendEvent(event: Object = {}) {
  475. if (this._enabled) {
  476. transport.sendEvent(event);
  477. }
  478. }
  479. /**
  480. * Notify external application (if API is enabled) that message was sent.
  481. *
  482. * @param {string} message - Message body.
  483. * @param {boolean} privateMessage - True if the message was a private message.
  484. * @returns {void}
  485. */
  486. notifySendingChatMessage(message: string, privateMessage: boolean) {
  487. this._sendEvent({
  488. name: 'outgoing-message',
  489. message,
  490. privateMessage
  491. });
  492. }
  493. /**
  494. * Notify external application that the video quality setting has changed.
  495. *
  496. * @param {number} videoQuality - The video quality. The number represents the maximum height of the video streams.
  497. * @returns {void}
  498. */
  499. notifyVideoQualityChanged(videoQuality: number) {
  500. this._sendEvent({
  501. name: 'video-quality-changed',
  502. videoQuality
  503. });
  504. }
  505. /**
  506. * Notify external application (if API is enabled) that message was
  507. * received.
  508. *
  509. * @param {Object} options - Object with the message properties.
  510. * @returns {void}
  511. */
  512. notifyReceivedChatMessage(
  513. { body, id, nick, ts }: {
  514. body: *, id: string, nick: string, ts: *
  515. } = {}) {
  516. if (APP.conference.isLocalId(id)) {
  517. return;
  518. }
  519. this._sendEvent({
  520. name: 'incoming-message',
  521. from: id,
  522. message: body,
  523. nick,
  524. stamp: ts
  525. });
  526. }
  527. /**
  528. * Notify external application (if API is enabled) that user joined the
  529. * conference.
  530. *
  531. * @param {string} id - User id.
  532. * @param {Object} props - The display name of the user.
  533. * @returns {void}
  534. */
  535. notifyUserJoined(id: string, props: Object) {
  536. this._sendEvent({
  537. name: 'participant-joined',
  538. id,
  539. ...props
  540. });
  541. }
  542. /**
  543. * Notify external application (if API is enabled) that user left the
  544. * conference.
  545. *
  546. * @param {string} id - User id.
  547. * @returns {void}
  548. */
  549. notifyUserLeft(id: string) {
  550. this._sendEvent({
  551. name: 'participant-left',
  552. id
  553. });
  554. }
  555. /**
  556. * Notify external application (if API is enabled) that the user role
  557. * has changed.
  558. *
  559. * @param {string} id - User id.
  560. * @param {string} role - The new user role.
  561. * @returns {void}
  562. */
  563. notifyUserRoleChanged(id: string, role: string) {
  564. this._sendEvent({
  565. name: 'participant-role-changed',
  566. id,
  567. role
  568. });
  569. }
  570. /**
  571. * Notify external application (if API is enabled) that user changed their
  572. * avatar.
  573. *
  574. * @param {string} id - User id.
  575. * @param {string} avatarURL - The new avatar URL of the participant.
  576. * @returns {void}
  577. */
  578. notifyAvatarChanged(id: string, avatarURL: string) {
  579. this._sendEvent({
  580. name: 'avatar-changed',
  581. avatarURL,
  582. id
  583. });
  584. }
  585. /**
  586. * Notify external application (if API is enabled) that user received
  587. * a text message through datachannels.
  588. *
  589. * @param {Object} data - The event data.
  590. * @returns {void}
  591. */
  592. notifyEndpointTextMessageReceived(data: Object) {
  593. this._sendEvent({
  594. name: 'endpoint-text-message-received',
  595. data
  596. });
  597. }
  598. /**
  599. * Notify external application (if API is enabled) that the device list has
  600. * changed.
  601. *
  602. * @param {Object} devices - The new device list.
  603. * @returns {void}
  604. */
  605. notifyDeviceListChanged(devices: Object) {
  606. this._sendEvent({
  607. name: 'device-list-changed',
  608. devices
  609. });
  610. }
  611. /**
  612. * Notify external application (if API is enabled) that user changed their
  613. * nickname.
  614. *
  615. * @param {string} id - User id.
  616. * @param {string} displayname - User nickname.
  617. * @param {string} formattedDisplayName - The display name shown in Jitsi
  618. * meet's UI for the user.
  619. * @returns {void}
  620. */
  621. notifyDisplayNameChanged(
  622. id: string,
  623. { displayName, formattedDisplayName }: Object) {
  624. this._sendEvent({
  625. name: 'display-name-change',
  626. displayname: displayName,
  627. formattedDisplayName,
  628. id
  629. });
  630. }
  631. /**
  632. * Notify external application (if API is enabled) that user changed their
  633. * email.
  634. *
  635. * @param {string} id - User id.
  636. * @param {string} email - The new email of the participant.
  637. * @returns {void}
  638. */
  639. notifyEmailChanged(
  640. id: string,
  641. { email }: Object) {
  642. this._sendEvent({
  643. name: 'email-change',
  644. email,
  645. id
  646. });
  647. }
  648. /**
  649. * Notify external application (if API is enabled) that the an error has been logged.
  650. *
  651. * @param {string} logLevel - The message log level.
  652. * @param {Array} args - Array of strings composing the log message.
  653. * @returns {void}
  654. */
  655. notifyLog(logLevel: string, args: Array<string>) {
  656. this._sendEvent({
  657. name: 'log',
  658. logLevel,
  659. args
  660. });
  661. }
  662. /**
  663. * Notify external application (if API is enabled) that the conference has
  664. * been joined.
  665. *
  666. * @param {string} roomName - The room name.
  667. * @param {string} id - The id of the local user.
  668. * @param {Object} props - The display name and avatar URL of the local
  669. * user.
  670. * @returns {void}
  671. */
  672. notifyConferenceJoined(roomName: string, id: string, props: Object) {
  673. this._sendEvent({
  674. name: 'video-conference-joined',
  675. roomName,
  676. id,
  677. ...props
  678. });
  679. }
  680. /**
  681. * Notify external application (if API is enabled) that local user has left the conference.
  682. *
  683. * @param {string} roomName - User id.
  684. * @returns {void}
  685. */
  686. notifyConferenceLeft(roomName: string) {
  687. this._sendEvent({
  688. name: 'video-conference-left',
  689. roomName
  690. });
  691. }
  692. /**
  693. * Notify external application (if API is enabled) that we are ready to be
  694. * closed.
  695. *
  696. * @returns {void}
  697. */
  698. notifyReadyToClose() {
  699. this._sendEvent({ name: 'video-ready-to-close' });
  700. }
  701. /**
  702. * Notify external application (if API is enabled) that a suspend event in host computer.
  703. *
  704. * @returns {void}
  705. */
  706. notifySuspendDetected() {
  707. this._sendEvent({ name: 'suspend-detected' });
  708. }
  709. /**
  710. * Notify external application (if API is enabled) for audio muted status
  711. * changed.
  712. *
  713. * @param {boolean} muted - The new muted status.
  714. * @returns {void}
  715. */
  716. notifyAudioMutedStatusChanged(muted: boolean) {
  717. this._sendEvent({
  718. name: 'audio-mute-status-changed',
  719. muted
  720. });
  721. }
  722. /**
  723. * Notify external application (if API is enabled) for video muted status
  724. * changed.
  725. *
  726. * @param {boolean} muted - The new muted status.
  727. * @returns {void}
  728. */
  729. notifyVideoMutedStatusChanged(muted: boolean) {
  730. this._sendEvent({
  731. name: 'video-mute-status-changed',
  732. muted
  733. });
  734. }
  735. /**
  736. * Notify external application (if API is enabled) for audio availability
  737. * changed.
  738. *
  739. * @param {boolean} available - True if available and false otherwise.
  740. * @returns {void}
  741. */
  742. notifyAudioAvailabilityChanged(available: boolean) {
  743. audioAvailable = available;
  744. this._sendEvent({
  745. name: 'audio-availability-changed',
  746. available
  747. });
  748. }
  749. /**
  750. * Notify external application (if API is enabled) for video available
  751. * status changed.
  752. *
  753. * @param {boolean} available - True if available and false otherwise.
  754. * @returns {void}
  755. */
  756. notifyVideoAvailabilityChanged(available: boolean) {
  757. videoAvailable = available;
  758. this._sendEvent({
  759. name: 'video-availability-changed',
  760. available
  761. });
  762. }
  763. /**
  764. * Notify external application (if API is enabled) that the on stage
  765. * participant has changed.
  766. *
  767. * @param {string} id - User id of the new on stage participant.
  768. * @returns {void}
  769. */
  770. notifyOnStageParticipantChanged(id: string) {
  771. this._sendEvent({
  772. name: 'on-stage-participant-changed',
  773. id
  774. });
  775. }
  776. /**
  777. * Notify external application of an unexpected camera-related error having
  778. * occurred.
  779. *
  780. * @param {string} type - The type of the camera error.
  781. * @param {string} message - Additional information about the error.
  782. * @returns {void}
  783. */
  784. notifyOnCameraError(type: string, message: string) {
  785. this._sendEvent({
  786. name: 'camera-error',
  787. type,
  788. message
  789. });
  790. }
  791. /**
  792. * Notify external application of an unexpected mic-related error having
  793. * occurred.
  794. *
  795. * @param {string} type - The type of the mic error.
  796. * @param {string} message - Additional information about the error.
  797. * @returns {void}
  798. */
  799. notifyOnMicError(type: string, message: string) {
  800. this._sendEvent({
  801. name: 'mic-error',
  802. type,
  803. message
  804. });
  805. }
  806. /**
  807. * Notify external application (if API is enabled) that conference feedback
  808. * has been submitted. Intended to be used in conjunction with the
  809. * submit-feedback command to get notified if feedback was submitted.
  810. *
  811. * @param {string} error - A failure message, if any.
  812. * @returns {void}
  813. */
  814. notifyFeedbackSubmitted(error: string) {
  815. this._sendEvent({
  816. name: 'feedback-submitted',
  817. error
  818. });
  819. }
  820. /**
  821. * Notify external application (if API is enabled) that the feedback prompt
  822. * has been displayed.
  823. *
  824. * @returns {void}
  825. */
  826. notifyFeedbackPromptDisplayed() {
  827. this._sendEvent({ name: 'feedback-prompt-displayed' });
  828. }
  829. /**
  830. * Notify external application (if API is enabled) that the display
  831. * configuration of the filmstrip has been changed.
  832. *
  833. * @param {boolean} visible - Whether or not the filmstrip has been set to
  834. * be displayed or hidden.
  835. * @returns {void}
  836. */
  837. notifyFilmstripDisplayChanged(visible: boolean) {
  838. this._sendEvent({
  839. name: 'filmstrip-display-changed',
  840. visible
  841. });
  842. }
  843. /**
  844. * Notify external application of a participant, remote or local, being
  845. * removed from the conference by another participant.
  846. *
  847. * @param {string} kicked - The ID of the participant removed from the
  848. * conference.
  849. * @param {string} kicker - The ID of the participant that removed the
  850. * other participant.
  851. * @returns {void}
  852. */
  853. notifyKickedOut(kicked: Object, kicker: Object) {
  854. this._sendEvent({
  855. name: 'participant-kicked-out',
  856. kicked,
  857. kicker
  858. });
  859. }
  860. /**
  861. * Notify external application of the current meeting requiring a password
  862. * to join.
  863. *
  864. * @returns {void}
  865. */
  866. notifyOnPasswordRequired() {
  867. this._sendEvent({ name: 'password-required' });
  868. }
  869. /**
  870. * Notify external application (if API is enabled) that the screen sharing
  871. * has been turned on/off.
  872. *
  873. * @param {boolean} on - True if screen sharing is enabled.
  874. * @param {Object} details - Additional information about the screen
  875. * sharing.
  876. * @param {string} details.sourceType - Type of device or window the screen
  877. * share is capturing.
  878. * @returns {void}
  879. */
  880. notifyScreenSharingStatusChanged(on: boolean, details: Object) {
  881. this._sendEvent({
  882. name: 'screen-sharing-status-changed',
  883. on,
  884. details
  885. });
  886. }
  887. /**
  888. * Notify external application (if API is enabled) that the dominant speaker
  889. * has been turned on/off.
  890. *
  891. * @param {string} id - Id of the dominant participant.
  892. * @returns {void}
  893. */
  894. notifyDominantSpeakerChanged(id: string) {
  895. this._sendEvent({
  896. name: 'dominant-speaker-changed',
  897. id
  898. });
  899. }
  900. /**
  901. * Notify external application (if API is enabled) that the conference
  902. * changed their subject.
  903. *
  904. * @param {string} subject - Conference subject.
  905. * @returns {void}
  906. */
  907. notifySubjectChanged(subject: string) {
  908. this._sendEvent({
  909. name: 'subject-change',
  910. subject
  911. });
  912. }
  913. /**
  914. * Notify external application (if API is enabled) that tile view has been
  915. * entered or exited.
  916. *
  917. * @param {string} enabled - True if tile view is currently displayed, false
  918. * otherwise.
  919. * @returns {void}
  920. */
  921. notifyTileViewChanged(enabled: boolean) {
  922. this._sendEvent({
  923. name: 'tile-view-changed',
  924. enabled
  925. });
  926. }
  927. /**
  928. * Disposes the allocated resources.
  929. *
  930. * @returns {void}
  931. */
  932. dispose() {
  933. if (this._enabled) {
  934. this._enabled = false;
  935. APP.conference.removeListener(
  936. JitsiMeetConferenceEvents.DESKTOP_SHARING_ENABLED_CHANGED,
  937. onDesktopSharingEnabledChanged);
  938. }
  939. }
  940. }
  941. export default new API();