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

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