您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

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