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

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