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

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