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

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