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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693
  1. // @flow
  2. import * as JitsiMeetConferenceEvents from '../../ConferenceEvents';
  3. import {
  4. createApiEvent,
  5. sendAnalytics
  6. } from '../../react/features/analytics';
  7. import { setSubject } from '../../react/features/base/conference';
  8. import { parseJWTFromURLParams } from '../../react/features/base/jwt';
  9. import { invite } from '../../react/features/invite';
  10. import { toggleTileView } from '../../react/features/video-layout';
  11. import { getJitsiMeetTransport } from '../transport';
  12. import { API_ID } from './constants';
  13. import {
  14. processExternalDeviceRequest
  15. } from '../../react/features/device-selection/functions';
  16. const logger = require('jitsi-meet-logger').getLogger(__filename);
  17. declare var APP: Object;
  18. /**
  19. * List of the available commands.
  20. */
  21. let commands = {};
  22. /**
  23. * The state of screen sharing(started/stopped) before the screen sharing is
  24. * enabled and initialized.
  25. * NOTE: This flag help us to cache the state and use it if toggle-share-screen
  26. * was received before the initialization.
  27. */
  28. let initialScreenSharingState = false;
  29. /**
  30. * The transport instance used for communication with external apps.
  31. *
  32. * @type {Transport}
  33. */
  34. const transport = getJitsiMeetTransport();
  35. /**
  36. * The current audio availability.
  37. *
  38. * @type {boolean}
  39. */
  40. let audioAvailable = true;
  41. /**
  42. * The current video availability.
  43. *
  44. * @type {boolean}
  45. */
  46. let videoAvailable = true;
  47. /**
  48. * Initializes supported commands.
  49. *
  50. * @returns {void}
  51. */
  52. function initCommands() {
  53. commands = {
  54. 'display-name': displayName => {
  55. sendAnalytics(createApiEvent('display.name.changed'));
  56. APP.conference.changeLocalDisplayName(displayName);
  57. },
  58. 'proxy-connection-event': event => {
  59. APP.conference.onProxyConnectionEvent(event);
  60. },
  61. 'subject': subject => {
  62. sendAnalytics(createApiEvent('subject.changed'));
  63. APP.store.dispatch(setSubject(subject));
  64. },
  65. 'submit-feedback': feedback => {
  66. sendAnalytics(createApiEvent('submit.feedback'));
  67. APP.conference.submitFeedback(feedback.score, feedback.message);
  68. },
  69. 'toggle-audio': () => {
  70. sendAnalytics(createApiEvent('toggle-audio'));
  71. logger.log('Audio toggle: API command received');
  72. APP.conference.toggleAudioMuted(false /* no UI */);
  73. },
  74. 'toggle-video': () => {
  75. sendAnalytics(createApiEvent('toggle-video'));
  76. logger.log('Video toggle: API command received');
  77. APP.conference.toggleVideoMuted(false /* no UI */);
  78. },
  79. 'toggle-film-strip': () => {
  80. sendAnalytics(createApiEvent('film.strip.toggled'));
  81. APP.UI.toggleFilmstrip();
  82. },
  83. 'toggle-chat': () => {
  84. sendAnalytics(createApiEvent('chat.toggled'));
  85. APP.UI.toggleChat();
  86. },
  87. 'toggle-share-screen': () => {
  88. sendAnalytics(createApiEvent('screen.sharing.toggled'));
  89. toggleScreenSharing();
  90. },
  91. 'toggle-tile-view': () => {
  92. sendAnalytics(createApiEvent('tile-view.toggled'));
  93. APP.store.dispatch(toggleTileView());
  94. },
  95. 'video-hangup': (showFeedbackDialog = true) => {
  96. sendAnalytics(createApiEvent('video.hangup'));
  97. APP.conference.hangup(showFeedbackDialog);
  98. },
  99. 'email': email => {
  100. sendAnalytics(createApiEvent('email.changed'));
  101. APP.conference.changeLocalEmail(email);
  102. },
  103. 'avatar-url': avatarUrl => {
  104. sendAnalytics(createApiEvent('avatar.url.changed'));
  105. APP.conference.changeLocalAvatarUrl(avatarUrl);
  106. }
  107. };
  108. transport.on('event', ({ data, name }) => {
  109. if (name && commands[name]) {
  110. commands[name](...data);
  111. return true;
  112. }
  113. return false;
  114. });
  115. transport.on('request', (request, callback) => {
  116. const { dispatch, getState } = APP.store;
  117. if (processExternalDeviceRequest(dispatch, getState, request, callback)) {
  118. return true;
  119. }
  120. const { name } = request;
  121. switch (name) {
  122. case 'invite': {
  123. const { invitees } = request;
  124. if (!Array.isArray(invitees) || invitees.length === 0) {
  125. callback({
  126. error: new Error('Unexpected format of invitees')
  127. });
  128. break;
  129. }
  130. // The store should be already available because API.init is called
  131. // on appWillMount action.
  132. APP.store.dispatch(
  133. invite(invitees, true))
  134. .then(failedInvitees => {
  135. let error;
  136. let result;
  137. if (failedInvitees.length) {
  138. error = new Error('One or more invites failed!');
  139. } else {
  140. result = true;
  141. }
  142. callback({
  143. error,
  144. result
  145. });
  146. });
  147. break;
  148. }
  149. case 'is-audio-muted':
  150. callback(APP.conference.isLocalAudioMuted());
  151. break;
  152. case 'is-video-muted':
  153. callback(APP.conference.isLocalVideoMuted());
  154. break;
  155. case 'is-audio-available':
  156. callback(audioAvailable);
  157. break;
  158. case 'is-video-available':
  159. callback(videoAvailable);
  160. break;
  161. default:
  162. return false;
  163. }
  164. return true;
  165. });
  166. }
  167. /**
  168. * Listens for desktop/screen sharing enabled events and toggles the screen
  169. * sharing if needed.
  170. *
  171. * @param {boolean} enabled - Current screen sharing enabled status.
  172. * @returns {void}
  173. */
  174. function onDesktopSharingEnabledChanged(enabled = false) {
  175. if (enabled && initialScreenSharingState) {
  176. toggleScreenSharing();
  177. }
  178. }
  179. /**
  180. * Check whether the API should be enabled or not.
  181. *
  182. * @returns {boolean}
  183. */
  184. function shouldBeEnabled() {
  185. return (
  186. typeof API_ID === 'number'
  187. // XXX Enable the API when a JSON Web Token (JWT) is specified in
  188. // the location/URL because then it is very likely that the Jitsi
  189. // Meet (Web) app is being used by an external/wrapping (Web) app
  190. // and, consequently, the latter will need to communicate with the
  191. // former. (The described logic is merely a heuristic though.)
  192. || parseJWTFromURLParams());
  193. }
  194. /**
  195. * Executes on toggle-share-screen command.
  196. *
  197. * @returns {void}
  198. */
  199. function toggleScreenSharing() {
  200. if (APP.conference.isDesktopSharingEnabled) {
  201. // eslint-disable-next-line no-empty-function
  202. APP.conference.toggleScreenSharing().catch(() => {});
  203. } else {
  204. initialScreenSharingState = !initialScreenSharingState;
  205. }
  206. }
  207. /**
  208. * Implements API class that communicates with external API class and provides
  209. * interface to access Jitsi Meet features by external applications that embed
  210. * Jitsi Meet.
  211. */
  212. class API {
  213. _enabled: boolean;
  214. /**
  215. * Initializes the API. Setups message event listeners that will receive
  216. * information from external applications that embed Jitsi Meet. It also
  217. * sends a message to the external application that API is initialized.
  218. *
  219. * @param {Object} options - Optional parameters.
  220. * @returns {void}
  221. */
  222. init() {
  223. if (!shouldBeEnabled()) {
  224. return;
  225. }
  226. /**
  227. * Current status (enabled/disabled) of API.
  228. *
  229. * @private
  230. * @type {boolean}
  231. */
  232. this._enabled = true;
  233. APP.conference.addListener(
  234. JitsiMeetConferenceEvents.DESKTOP_SHARING_ENABLED_CHANGED,
  235. onDesktopSharingEnabledChanged);
  236. initCommands();
  237. }
  238. /**
  239. * Notify external application (if API is enabled) that the large video
  240. * visibility changed.
  241. *
  242. * @param {boolean} isHidden - True if the large video is hidden and false
  243. * otherwise.
  244. * @returns {void}
  245. */
  246. notifyLargeVideoVisibilityChanged(isHidden: boolean) {
  247. this._sendEvent({
  248. name: 'large-video-visibility-changed',
  249. isVisible: !isHidden
  250. });
  251. }
  252. /**
  253. * Notifies the external application (spot) that the local jitsi-participant
  254. * has a status update.
  255. *
  256. * @param {Object} event - The message to pass onto spot.
  257. * @returns {void}
  258. */
  259. sendProxyConnectionEvent(event: Object) {
  260. this._sendEvent({
  261. name: 'proxy-connection-event',
  262. ...event
  263. });
  264. }
  265. /**
  266. * Sends event to the external application.
  267. *
  268. * @param {Object} event - The event to be sent.
  269. * @returns {void}
  270. */
  271. _sendEvent(event: Object = {}) {
  272. if (this._enabled) {
  273. transport.sendEvent(event);
  274. }
  275. }
  276. /**
  277. * Notify external application (if API is enabled) that message was sent.
  278. *
  279. * @param {string} message - Message body.
  280. * @returns {void}
  281. */
  282. notifySendingChatMessage(message: string) {
  283. this._sendEvent({
  284. name: 'outgoing-message',
  285. message
  286. });
  287. }
  288. /**
  289. * Notify external application (if API is enabled) that message was
  290. * received.
  291. *
  292. * @param {Object} options - Object with the message properties.
  293. * @returns {void}
  294. */
  295. notifyReceivedChatMessage(
  296. { body, id, nick, ts }: {
  297. body: *, id: string, nick: string, ts: *
  298. } = {}) {
  299. if (APP.conference.isLocalId(id)) {
  300. return;
  301. }
  302. this._sendEvent({
  303. name: 'incoming-message',
  304. from: id,
  305. message: body,
  306. nick,
  307. stamp: ts
  308. });
  309. }
  310. /**
  311. * Notify external application (if API is enabled) that user joined the
  312. * conference.
  313. *
  314. * @param {string} id - User id.
  315. * @param {Object} props - The display name of the user.
  316. * @returns {void}
  317. */
  318. notifyUserJoined(id: string, props: Object) {
  319. this._sendEvent({
  320. name: 'participant-joined',
  321. id,
  322. ...props
  323. });
  324. }
  325. /**
  326. * Notify external application (if API is enabled) that user left the
  327. * conference.
  328. *
  329. * @param {string} id - User id.
  330. * @returns {void}
  331. */
  332. notifyUserLeft(id: string) {
  333. this._sendEvent({
  334. name: 'participant-left',
  335. id
  336. });
  337. }
  338. /**
  339. * Notify external application (if API is enabled) that user changed their
  340. * avatar.
  341. *
  342. * @param {string} id - User id.
  343. * @param {string} avatarURL - The new avatar URL of the participant.
  344. * @returns {void}
  345. */
  346. notifyAvatarChanged(id: string, avatarURL: string) {
  347. this._sendEvent({
  348. name: 'avatar-changed',
  349. avatarURL,
  350. id
  351. });
  352. }
  353. /**
  354. * Notify external application (if API is enabled) that the device list has
  355. * changed.
  356. *
  357. * @param {Object} devices - The new device list.
  358. * @returns {void}
  359. */
  360. notifyDeviceListChanged(devices: Object) {
  361. this._sendEvent({
  362. name: 'device-list-changed',
  363. devices });
  364. }
  365. /**
  366. * Notify external application (if API is enabled) that user changed their
  367. * nickname.
  368. *
  369. * @param {string} id - User id.
  370. * @param {string} displayname - User nickname.
  371. * @param {string} formattedDisplayName - The display name shown in Jitsi
  372. * meet's UI for the user.
  373. * @returns {void}
  374. */
  375. notifyDisplayNameChanged(
  376. id: string,
  377. { displayName, formattedDisplayName }: Object) {
  378. this._sendEvent({
  379. name: 'display-name-change',
  380. displayname: displayName,
  381. formattedDisplayName,
  382. id
  383. });
  384. }
  385. /**
  386. * Notify external application (if API is enabled) that user changed their
  387. * email.
  388. *
  389. * @param {string} id - User id.
  390. * @param {string} email - The new email of the participant.
  391. * @returns {void}
  392. */
  393. notifyEmailChanged(
  394. id: string,
  395. { email }: Object) {
  396. this._sendEvent({
  397. name: 'email-change',
  398. email,
  399. id
  400. });
  401. }
  402. /**
  403. * Notify external application (if API is enabled) that the conference has
  404. * been joined.
  405. *
  406. * @param {string} roomName - The room name.
  407. * @param {string} id - The id of the local user.
  408. * @param {Object} props - The display name and avatar URL of the local
  409. * user.
  410. * @returns {void}
  411. */
  412. notifyConferenceJoined(roomName: string, id: string, props: Object) {
  413. this._sendEvent({
  414. name: 'video-conference-joined',
  415. roomName,
  416. id,
  417. ...props
  418. });
  419. }
  420. /**
  421. * Notify external application (if API is enabled) that user changed their
  422. * nickname.
  423. *
  424. * @param {string} roomName - User id.
  425. * @returns {void}
  426. */
  427. notifyConferenceLeft(roomName: string) {
  428. this._sendEvent({
  429. name: 'video-conference-left',
  430. roomName
  431. });
  432. }
  433. /**
  434. * Notify external application (if API is enabled) that we are ready to be
  435. * closed.
  436. *
  437. * @returns {void}
  438. */
  439. notifyReadyToClose() {
  440. this._sendEvent({ name: 'video-ready-to-close' });
  441. }
  442. /**
  443. * Notify external application (if API is enabled) for audio muted status
  444. * changed.
  445. *
  446. * @param {boolean} muted - The new muted status.
  447. * @returns {void}
  448. */
  449. notifyAudioMutedStatusChanged(muted: boolean) {
  450. this._sendEvent({
  451. name: 'audio-mute-status-changed',
  452. muted
  453. });
  454. }
  455. /**
  456. * Notify external application (if API is enabled) for video muted status
  457. * changed.
  458. *
  459. * @param {boolean} muted - The new muted status.
  460. * @returns {void}
  461. */
  462. notifyVideoMutedStatusChanged(muted: boolean) {
  463. this._sendEvent({
  464. name: 'video-mute-status-changed',
  465. muted
  466. });
  467. }
  468. /**
  469. * Notify external application (if API is enabled) for audio availability
  470. * changed.
  471. *
  472. * @param {boolean} available - True if available and false otherwise.
  473. * @returns {void}
  474. */
  475. notifyAudioAvailabilityChanged(available: boolean) {
  476. audioAvailable = available;
  477. this._sendEvent({
  478. name: 'audio-availability-changed',
  479. available
  480. });
  481. }
  482. /**
  483. * Notify external application (if API is enabled) for video available
  484. * status changed.
  485. *
  486. * @param {boolean} available - True if available and false otherwise.
  487. * @returns {void}
  488. */
  489. notifyVideoAvailabilityChanged(available: boolean) {
  490. videoAvailable = available;
  491. this._sendEvent({
  492. name: 'video-availability-changed',
  493. available
  494. });
  495. }
  496. /**
  497. * Notify external application (if API is enabled) that the on stage
  498. * participant has changed.
  499. *
  500. * @param {string} id - User id of the new on stage participant.
  501. * @returns {void}
  502. */
  503. notifyOnStageParticipantChanged(id: string) {
  504. this._sendEvent({
  505. name: 'on-stage-participant-changed',
  506. id
  507. });
  508. }
  509. /**
  510. * Notify external application of an unexpected camera-related error having
  511. * occurred.
  512. *
  513. * @param {string} type - The type of the camera error.
  514. * @param {string} message - Additional information about the error.
  515. * @returns {void}
  516. */
  517. notifyOnCameraError(type: string, message: string) {
  518. this._sendEvent({
  519. name: 'camera-error',
  520. type,
  521. message
  522. });
  523. }
  524. /**
  525. * Notify external application of an unexpected mic-related error having
  526. * occurred.
  527. *
  528. * @param {string} type - The type of the mic error.
  529. * @param {string} message - Additional information about the error.
  530. * @returns {void}
  531. */
  532. notifyOnMicError(type: string, message: string) {
  533. this._sendEvent({
  534. name: 'mic-error',
  535. type,
  536. message
  537. });
  538. }
  539. /**
  540. * Notify external application (if API is enabled) that conference feedback
  541. * has been submitted. Intended to be used in conjunction with the
  542. * submit-feedback command to get notified if feedback was submitted.
  543. *
  544. * @returns {void}
  545. */
  546. notifyFeedbackSubmitted() {
  547. this._sendEvent({ name: 'feedback-submitted' });
  548. }
  549. /**
  550. * Notify external application (if API is enabled) that the feedback prompt
  551. * has been displayed.
  552. *
  553. * @returns {void}
  554. */
  555. notifyFeedbackPromptDisplayed() {
  556. this._sendEvent({ name: 'feedback-prompt-displayed' });
  557. }
  558. /**
  559. * Notify external application (if API is enabled) that the display
  560. * configuration of the filmstrip has been changed.
  561. *
  562. * @param {boolean} visible - Whether or not the filmstrip has been set to
  563. * be displayed or hidden.
  564. * @returns {void}
  565. */
  566. notifyFilmstripDisplayChanged(visible: boolean) {
  567. this._sendEvent({
  568. name: 'filmstrip-display-changed',
  569. visible
  570. });
  571. }
  572. /**
  573. * Notify external application (if API is enabled) that the screen sharing
  574. * has been turned on/off.
  575. *
  576. * @param {boolean} on - True if screen sharing is enabled.
  577. * @param {Object} details - Additional information about the screen
  578. * sharing.
  579. * @param {string} details.sourceType - Type of device or window the screen
  580. * share is capturing.
  581. * @returns {void}
  582. */
  583. notifyScreenSharingStatusChanged(on: boolean, details: Object) {
  584. this._sendEvent({
  585. name: 'screen-sharing-status-changed',
  586. on,
  587. details
  588. });
  589. }
  590. /**
  591. * Notify external application (if API is enabled) that the conference
  592. * changed their subject.
  593. *
  594. * @param {string} subject - Conference subject.
  595. * @returns {void}
  596. */
  597. notifySubjectChanged(subject: string) {
  598. this._sendEvent({
  599. name: 'subject-change',
  600. subject
  601. });
  602. }
  603. /**
  604. * Notify external application (if API is enabled) that tile view has been
  605. * entered or exited.
  606. *
  607. * @param {string} enabled - True if tile view is currently displayed, false
  608. * otherwise.
  609. * @returns {void}
  610. */
  611. notifyTileViewChanged(enabled: boolean) {
  612. this._sendEvent({
  613. name: 'tile-view-changed',
  614. enabled
  615. });
  616. }
  617. /**
  618. * Disposes the allocated resources.
  619. *
  620. * @returns {void}
  621. */
  622. dispose() {
  623. if (this._enabled) {
  624. this._enabled = false;
  625. APP.conference.removeListener(
  626. JitsiMeetConferenceEvents.DESKTOP_SHARING_ENABLED_CHANGED,
  627. onDesktopSharingEnabledChanged);
  628. }
  629. }
  630. }
  631. export default new API();