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.

ConnectionStatsTable.js 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646
  1. /* @flow */
  2. import React, { Component } from 'react';
  3. import { translate } from '../../base/i18n';
  4. /**
  5. * The type of the React {@code Component} props of
  6. * {@link ConnectionStatsTable}.
  7. */
  8. type Props = {
  9. /**
  10. * Statistics related to bandwidth.
  11. * {{
  12. * download: Number,
  13. * upload: Number
  14. * }}
  15. */
  16. bandwidth: Object,
  17. /**
  18. * Statistics related to bitrate.
  19. * {{
  20. * download: Number,
  21. * upload: Number
  22. * }}
  23. */
  24. bitrate: Object,
  25. /**
  26. * The number of bridges (aka media servers) currently used in the
  27. * conference.
  28. */
  29. bridgeCount: number,
  30. /**
  31. * A message describing the connection quality.
  32. */
  33. connectionSummary: string,
  34. /**
  35. * The end-to-end round-trip-time.
  36. */
  37. e2eRtt: number,
  38. /**
  39. * Statistics related to frame rates for each ssrc.
  40. * {{
  41. * [ ssrc ]: Number
  42. * }}
  43. */
  44. framerate: Object,
  45. /**
  46. * Whether or not the statistics are for local video.
  47. */
  48. isLocalVideo: boolean,
  49. /**
  50. * Callback to invoke when the show additional stats link is clicked.
  51. */
  52. onShowMore: Function,
  53. /**
  54. * Statistics related to packet loss.
  55. * {{
  56. * download: Number,
  57. * upload: Number
  58. * }}
  59. */
  60. packetLoss: Object,
  61. /**
  62. * The region that we think the client is in.
  63. */
  64. region: string,
  65. /**
  66. * Statistics related to display resolutions for each ssrc.
  67. * {{
  68. * [ ssrc ]: {
  69. * height: Number,
  70. * width: Number
  71. * }
  72. * }}
  73. */
  74. resolution: Object,
  75. /**
  76. * The region of the media server that we are connected to.
  77. */
  78. serverRegion: string,
  79. /**
  80. * Whether or not additional stats about bandwidth and transport should be
  81. * displayed. Will not display even if true for remote participants.
  82. */
  83. shouldShowMore: boolean,
  84. /**
  85. * Invoked to obtain translated strings.
  86. */
  87. t: Function,
  88. /**
  89. * Statistics related to transports.
  90. */
  91. transport: Array<Object>
  92. };
  93. /**
  94. * React {@code Component} for displaying connection statistics.
  95. *
  96. * @extends Component
  97. */
  98. class ConnectionStatsTable extends Component<Props> {
  99. /**
  100. * Implements React's {@link Component#render()}.
  101. *
  102. * @inheritdoc
  103. * @returns {ReactElement}
  104. */
  105. render() {
  106. const { isLocalVideo } = this.props;
  107. return (
  108. <div className = 'connection-info'>
  109. { this._renderStatistics() }
  110. { isLocalVideo ? this._renderShowMoreLink() : null }
  111. { isLocalVideo && this.props.shouldShowMore
  112. ? this._renderAdditionalStats() : null }
  113. </div>
  114. );
  115. }
  116. /**
  117. * Creates a table as ReactElement that will display additional statistics
  118. * related to bandwidth and transport for the local user.
  119. *
  120. * @private
  121. * @returns {ReactElement}
  122. */
  123. _renderAdditionalStats() {
  124. return (
  125. <table className = 'connection-info__container'>
  126. <tbody>
  127. { this._renderBandwidth() }
  128. { this._renderTransport() }
  129. { this._renderRegion() }
  130. </tbody>
  131. </table>
  132. );
  133. }
  134. /**
  135. * Creates a table row as a ReactElement for displaying bandwidth related
  136. * statistics.
  137. *
  138. * @private
  139. * @returns {ReactElement}
  140. */
  141. _renderBandwidth() {
  142. const { download, upload } = this.props.bandwidth || {};
  143. return (
  144. <tr>
  145. <td>
  146. { this.props.t('connectionindicator.bandwidth') }
  147. </td>
  148. <td>
  149. <span className = 'connection-info__download'>
  150. &darr;
  151. </span>
  152. { download ? `${download} Kbps` : 'N/A' }
  153. <span className = 'connection-info__upload'>
  154. &uarr;
  155. </span>
  156. { upload ? `${upload} Kbps` : 'N/A' }
  157. </td>
  158. </tr>
  159. );
  160. }
  161. /**
  162. * Creates a a table row as a ReactElement for displaying bitrate related
  163. * statistics.
  164. *
  165. * @private
  166. * @returns {ReactElement}
  167. */
  168. _renderBitrate() {
  169. const { download, upload } = this.props.bitrate || {};
  170. return (
  171. <tr>
  172. <td>
  173. <span>
  174. { this.props.t('connectionindicator.bitrate') }
  175. </span>
  176. </td>
  177. <td>
  178. <span className = 'connection-info__download'>
  179. &darr;
  180. </span>
  181. { download ? `${download} Kbps` : 'N/A' }
  182. <span className = 'connection-info__upload'>
  183. &uarr;
  184. </span>
  185. { upload ? `${upload} Kbps` : 'N/A' }
  186. </td>
  187. </tr>
  188. );
  189. }
  190. /**
  191. * Creates a table row as a ReactElement for displaying a summary message
  192. * about the current connection status.
  193. *
  194. * @private
  195. * @returns {ReactElement}
  196. */
  197. _renderConnectionSummary() {
  198. return (
  199. <tr className = 'connection-info__status'>
  200. <td>
  201. <span>{ this.props.t('connectionindicator.status') }</span>
  202. </td>
  203. <td>{ this.props.connectionSummary }</td>
  204. </tr>
  205. );
  206. }
  207. /**
  208. * Creates a table row as a ReactElement for displaying end-to-end RTT and
  209. * the region.
  210. *
  211. * @returns {ReactElement}
  212. * @private
  213. */
  214. _renderE2eRtt() {
  215. const { e2eRtt, t } = this.props;
  216. const str = e2eRtt ? `${e2eRtt.toFixed(0)}ms` : 'N/A';
  217. return (
  218. <tr>
  219. <td>
  220. <span>{ t('connectionindicator.e2e_rtt') }</span>
  221. </td>
  222. <td>{ str }</td>
  223. </tr>
  224. );
  225. }
  226. /**
  227. * Creates a table row as a ReactElement for displaying the "connected to"
  228. * information.
  229. *
  230. * @returns {ReactElement}
  231. * @private
  232. */
  233. _renderRegion() {
  234. const { region, serverRegion, t } = this.props;
  235. let str = serverRegion;
  236. if (!serverRegion) {
  237. return;
  238. }
  239. if (region && serverRegion && region !== serverRegion) {
  240. str += ` from ${region}`;
  241. }
  242. return (
  243. <tr>
  244. <td>
  245. <span>{ t('connectionindicator.connectedTo') }</span>
  246. </td>
  247. <td>{ str }</td>
  248. </tr>
  249. );
  250. }
  251. /**
  252. * Creates a table row as a ReactElement for displaying the "bridge count"
  253. * information.
  254. *
  255. * @returns {*}
  256. * @private
  257. */
  258. _renderBridgeCount() {
  259. const { bridgeCount, t } = this.props;
  260. // 0 is valid, but undefined/null/NaN aren't.
  261. if (!bridgeCount && bridgeCount !== 0) {
  262. return;
  263. }
  264. return (
  265. <tr>
  266. <td>
  267. <span>{ t('connectionindicator.bridgeCount') }</span>
  268. </td>
  269. <td>{ bridgeCount }</td>
  270. </tr>
  271. );
  272. }
  273. /**
  274. * Creates a table row as a ReactElement for displaying frame rate related
  275. * statistics.
  276. *
  277. * @private
  278. * @returns {ReactElement}
  279. */
  280. _renderFrameRate() {
  281. const { framerate, t } = this.props;
  282. const frameRateString = Object.keys(framerate || {})
  283. .map(ssrc => framerate[ssrc])
  284. .join(', ') || 'N/A';
  285. return (
  286. <tr>
  287. <td>
  288. <span>{ t('connectionindicator.framerate') }</span>
  289. </td>
  290. <td>{ frameRateString }</td>
  291. </tr>
  292. );
  293. }
  294. /**
  295. * Creates a tables row as a ReactElement for displaying packet loss related
  296. * statistics.
  297. *
  298. * @private
  299. * @returns {ReactElement}
  300. */
  301. _renderPacketLoss() {
  302. const { packetLoss, t } = this.props;
  303. let packetLossTableData;
  304. if (packetLoss) {
  305. const { download, upload } = packetLoss;
  306. packetLossTableData = (
  307. <td>
  308. <span className = 'connection-info__download'>
  309. &darr;
  310. </span>
  311. { download === null ? 'N/A' : `${download}%` }
  312. <span className = 'connection-info__upload'>
  313. &uarr;
  314. </span>
  315. { upload === null ? 'N/A' : `${upload}%` }
  316. </td>
  317. );
  318. } else {
  319. packetLossTableData = <td>N/A</td>;
  320. }
  321. return (
  322. <tr>
  323. <td>
  324. <span>
  325. { t('connectionindicator.packetloss') }
  326. </span>
  327. </td>
  328. { packetLossTableData }
  329. </tr>
  330. );
  331. }
  332. /**
  333. * Creates a table row as a ReactElement for displaying resolution related
  334. * statistics.
  335. *
  336. * @private
  337. * @returns {ReactElement}
  338. */
  339. _renderResolution() {
  340. const { resolution, t } = this.props;
  341. const resolutionString = Object.keys(resolution || {})
  342. .map(ssrc => {
  343. const { width, height } = resolution[ssrc];
  344. return `${width}x${height}`;
  345. })
  346. .join(', ') || 'N/A';
  347. return (
  348. <tr>
  349. <td>
  350. <span>{ t('connectionindicator.resolution') }</span>
  351. </td>
  352. <td>{ resolutionString }</td>
  353. </tr>
  354. );
  355. }
  356. /**
  357. * Creates a ReactElement for display a link to toggle showing additional
  358. * statistics.
  359. *
  360. * @private
  361. * @returns {ReactElement}
  362. */
  363. _renderShowMoreLink() {
  364. const translationKey
  365. = this.props.shouldShowMore
  366. ? 'connectionindicator.less'
  367. : 'connectionindicator.more';
  368. return (
  369. <a
  370. className = 'showmore link'
  371. onClick = { this.props.onShowMore } >
  372. { this.props.t(translationKey) }
  373. </a>
  374. );
  375. }
  376. /**
  377. * Creates a table as a ReactElement for displaying connection statistics.
  378. *
  379. * @private
  380. * @returns {ReactElement}
  381. */
  382. _renderStatistics() {
  383. const isRemoteVideo = !this.props.isLocalVideo;
  384. return (
  385. <table className = 'connection-info__container'>
  386. <tbody>
  387. { this._renderConnectionSummary() }
  388. { this._renderBitrate() }
  389. { this._renderPacketLoss() }
  390. { isRemoteVideo ? this._renderE2eRtt() : null }
  391. { isRemoteVideo ? this._renderRegion() : null }
  392. { this._renderResolution() }
  393. { this._renderFrameRate() }
  394. { isRemoteVideo ? null : this._renderBridgeCount() }
  395. </tbody>
  396. </table>
  397. );
  398. }
  399. /**
  400. * Creates table rows as ReactElements for displaying transport related
  401. * statistics.
  402. *
  403. * @private
  404. * @returns {ReactElement[]}
  405. */
  406. _renderTransport() {
  407. const { t, transport } = this.props;
  408. if (!transport || transport.length === 0) {
  409. const NA = (
  410. <tr key = 'address'>
  411. <td>
  412. <span>{ t('connectionindicator.address') }</span>
  413. </td>
  414. <td>
  415. N/A
  416. </td>
  417. </tr>
  418. );
  419. return [ NA ];
  420. }
  421. const data = {
  422. localIP: [],
  423. localPort: [],
  424. remoteIP: [],
  425. remotePort: [],
  426. transportType: []
  427. };
  428. for (let i = 0; i < transport.length; i++) {
  429. const ip = getIP(transport[i].ip);
  430. const localIP = getIP(transport[i].localip);
  431. const localPort = getPort(transport[i].localip);
  432. const port = getPort(transport[i].ip);
  433. if (!data.remoteIP.includes(ip)) {
  434. data.remoteIP.push(ip);
  435. }
  436. if (!data.localIP.includes(localIP)) {
  437. data.localIP.push(localIP);
  438. }
  439. if (!data.localPort.includes(localPort)) {
  440. data.localPort.push(localPort);
  441. }
  442. if (!data.remotePort.includes(port)) {
  443. data.remotePort.push(port);
  444. }
  445. if (!data.transportType.includes(transport[i].type)) {
  446. data.transportType.push(transport[i].type);
  447. }
  448. }
  449. // All of the transports should be either P2P or JVB
  450. let isP2P = false, isTURN = false;
  451. if (transport.length) {
  452. isP2P = transport[0].p2p;
  453. isTURN = transport[0].localCandidateType === 'relay'
  454. || transport[0].remoteCandidateType === 'relay';
  455. }
  456. let additionalData = null;
  457. if (isP2P) {
  458. additionalData = isTURN
  459. ? <span>{ t('connectionindicator.turn') }</span>
  460. : <span>{ t('connectionindicator.peer_to_peer') }</span>;
  461. }
  462. // First show remote statistics, then local, and then transport type.
  463. const tableRowConfigurations = [
  464. {
  465. additionalData,
  466. data: data.remoteIP,
  467. key: 'remoteaddress',
  468. label: t('connectionindicator.remoteaddress',
  469. { count: data.remoteIP.length })
  470. },
  471. {
  472. data: data.remotePort,
  473. key: 'remoteport',
  474. label: t('connectionindicator.remoteport',
  475. { count: transport.length })
  476. },
  477. {
  478. data: data.localIP,
  479. key: 'localaddress',
  480. label: t('connectionindicator.localaddress',
  481. { count: data.localIP.length })
  482. },
  483. {
  484. data: data.localPort,
  485. key: 'localport',
  486. label: t('connectionindicator.localport',
  487. { count: transport.length })
  488. },
  489. {
  490. data: data.transportType,
  491. key: 'transport',
  492. label: t('connectionindicator.transport',
  493. { count: data.transportType.length })
  494. }
  495. ];
  496. return tableRowConfigurations.map(this._renderTransportTableRow);
  497. }
  498. /**
  499. * Creates a table row as a ReactElement for displaying a transport related
  500. * statistic.
  501. *
  502. * @param {Object} config - Describes the contents of the row.
  503. * @param {ReactElement} config.additionalData - Extra data to display next
  504. * to the passed in config.data.
  505. * @param {Array} config.data - The transport statistics to display.
  506. * @param {string} config.key - The ReactElement's key. Must be unique for
  507. * iterating over multiple child rows.
  508. * @param {string} config.label - The text to display describing the data.
  509. * @private
  510. * @returns {ReactElement}
  511. */
  512. _renderTransportTableRow(config: Object) {
  513. const { additionalData, data, key, label } = config;
  514. return (
  515. <tr key = { key }>
  516. <td>
  517. <span>
  518. { label }
  519. </span>
  520. </td>
  521. <td>
  522. { getStringFromArray(data) }
  523. { additionalData || null }
  524. </td>
  525. </tr>
  526. );
  527. }
  528. }
  529. /**
  530. * Utility for getting the IP from a transport statistics object's
  531. * representation of an IP.
  532. *
  533. * @param {string} value - The transport's IP to parse.
  534. * @private
  535. * @returns {string}
  536. */
  537. function getIP(value) {
  538. if (!value) {
  539. return '';
  540. }
  541. return value.substring(0, value.lastIndexOf(':'));
  542. }
  543. /**
  544. * Utility for getting the port from a transport statistics object's
  545. * representation of an IP.
  546. *
  547. * @param {string} value - The transport's IP to parse.
  548. * @private
  549. * @returns {string}
  550. */
  551. function getPort(value) {
  552. if (!value) {
  553. return '';
  554. }
  555. return value.substring(value.lastIndexOf(':') + 1, value.length);
  556. }
  557. /**
  558. * Utility for concatenating values in an array into a comma separated string.
  559. *
  560. * @param {Array} array - Transport statistics to concatenate.
  561. * @private
  562. * @returns {string}
  563. */
  564. function getStringFromArray(array) {
  565. let res = '';
  566. for (let i = 0; i < array.length; i++) {
  567. res += (i === 0 ? '' : ', ') + array[i];
  568. }
  569. return res;
  570. }
  571. export default translate(ConnectionStatsTable);