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

ConnectionStatsTable.js 17KB

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