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

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