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

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